@unbrained/pm-web 2026.6.2 → 2026.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +92 -1
  3. package/dist/auth.js +6 -0
  4. package/dist/auth.js.map +1 -1
  5. package/dist/board.js +27 -7
  6. package/dist/board.js.map +1 -1
  7. package/dist/ical.js +141 -0
  8. package/dist/ical.js.map +1 -0
  9. package/dist/index.js +351 -6
  10. package/dist/index.js.map +1 -1
  11. package/dist/routes/admin.js +11 -10
  12. package/dist/routes/admin.js.map +1 -1
  13. package/dist/routes/github.js +19 -18
  14. package/dist/routes/github.js.map +1 -1
  15. package/dist/routes/groups.js +11 -10
  16. package/dist/routes/groups.js.map +1 -1
  17. package/dist/routes/pm.js +241 -197
  18. package/dist/routes/pm.js.map +1 -1
  19. package/dist/routes/projects.js +4 -3
  20. package/dist/routes/projects.js.map +1 -1
  21. package/dist/routes/route-params.js +7 -0
  22. package/dist/routes/route-params.js.map +1 -0
  23. package/dist/routes/sharing.js +5 -4
  24. package/dist/routes/sharing.js.map +1 -1
  25. package/dist/server.js +13 -2
  26. package/dist/server.js.map +1 -1
  27. package/manifest.json +1 -1
  28. package/package.json +13 -13
  29. package/public/index.html +2 -1
  30. package/public/src/app.js +12 -1
  31. package/public/src/app.js.map +1 -1
  32. package/public/src/app.ts +12 -1
  33. package/public/src/filters.js +40 -0
  34. package/public/src/filters.js.map +1 -0
  35. package/public/src/filters.ts +52 -0
  36. package/public/src/state.js +1 -1
  37. package/public/src/state.js.map +1 -1
  38. package/public/src/state.ts +1 -1
  39. package/public/src/theme.js +74 -0
  40. package/public/src/theme.js.map +1 -0
  41. package/public/src/theme.ts +75 -0
  42. package/public/src/types.ts +1 -0
  43. package/public/src/utils.js +3 -1
  44. package/public/src/utils.js.map +1 -1
  45. package/public/src/utils.ts +3 -1
  46. package/public/src/views/calendar.js +1 -0
  47. package/public/src/views/calendar.js.map +1 -1
  48. package/public/src/views/calendar.ts +1 -0
  49. package/public/src/views/items.js +56 -3
  50. package/public/src/views/items.js.map +1 -1
  51. package/public/src/views/items.ts +55 -3
  52. package/public/styles.css +85 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.6.9 - 2026-06-09
4
+
5
+ ### Added
6
+
7
+ - Normalize board and search behavior for real pm data ([pm-web-hzb0](https://github.com/unbraind/pm-web/blob/main/.agents/pm/features/pm-web-hzb0.toon))
8
+
9
+ ### Removed
10
+
11
+ - Restore CI and Daily Release workflows removed by no-runner policy misapplication ([pm-web-2xlv](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-2xlv.toon))
12
+
13
+ ### Other
14
+
15
+ - Harden web release readiness checks ([pm-web-d5ef](https://github.com/unbraind/pm-web/blob/main/.agents/pm/chores/pm-web-d5ef.toon))
16
+ - Align package dependencies to pm CLI/SDK 2026.6.6 ([pm-web-9z1i](https://github.com/unbraind/pm-web/blob/main/.agents/pm/chores/pm-web-9z1i.toon))
17
+
18
+ ## 2026.06.04 - 2026-06-04
19
+
20
+ ### Added
21
+
22
+ - Add light/auto theme toggle, URL-state item filters, and iCal \(.ics\) calendar export ([pm-web-b6eo](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-b6eo.toon))
23
+
24
+ ## 2026.06.02-1 - 2026-06-02
25
+
26
+ ### Added
27
+
28
+ - Do NOT add the 'services' extension capability to pm-web ([pm-web-vyfp](https://github.com/unbraind/pm-web/blob/main/.agents/pm/decisions/pm-web-vyfp.toon))
29
+ - Deepen pm-web extension command surface \(status/stop/doctor\) + services-capability evaluation ([pm-web-7pxa](https://github.com/unbraind/pm-web/blob/main/.agents/pm/features/pm-web-7pxa.toon))
30
+ - Add /healthz version + unit tests + README docs ([pm-web-edyj](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-edyj.toon))
31
+ - Introduce CommandError \(numeric exitCode\) for new handlers ([pm-web-9ycn](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-9ycn.toon))
32
+
33
+ ### Other
34
+
35
+ - Implement 'pm web doctor' command ([pm-web-vyqb](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-vyqb.toon))
36
+ - Implement 'pm web stop' command + pidfile-on-detach ([pm-web-odwq](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-odwq.toon))
37
+ - Implement 'pm web status' command ([pm-web-8eqs](https://github.com/unbraind/pm-web/blob/main/.agents/pm/tasks/pm-web-8eqs.toon))
38
+
3
39
  ## 2026.05.30 - 2026-05-30
4
40
 
5
41
  ### Fixed
package/README.md CHANGED
@@ -52,8 +52,55 @@ The package repository is at **github.com/unbraind/pm-web**.
52
52
 
53
53
  | Command | Description |
54
54
  |---|---|
55
- | `pm web` | Start the pm-web server |
55
+ | `pm web` | Start the pm-web server (foreground) |
56
56
  | `pm web --port 8080` | Start on a custom port |
57
+ | `pm web --detach` | Start the server in the background (tracked via a pidfile) |
58
+ | `pm web status` | Report whether a server is reachable (probes `/healthz`); `--json` supported |
59
+ | `pm web stop` | Stop a server previously started with `--detach`; `--json` supported |
60
+ | `pm web doctor` | Preflight diagnostics (Node, runtime deps, port, pm, workspace); `--json` supported |
61
+
62
+ #### `pm web status`
63
+
64
+ Probes `http://localhost:<port>/healthz` and reports `up`/`down`, the responding
65
+ port, and the server version. Never errors when the server is down — it returns a
66
+ structured `down` result. The port is resolved from `--port`, then `PORT`, then
67
+ the default `4000`.
68
+
69
+ ```bash
70
+ pm web status # human-readable
71
+ pm web status --port 8080 --json
72
+ ```
73
+
74
+ #### `pm web stop`
75
+
76
+ Stops a server started with `pm web --detach`. The detached PID is recorded in a
77
+ pidfile (under `PM_WEB_STATE_DIR` if set, otherwise the OS temp dir, keyed by
78
+ port). `pm web stop` reads the pidfile, sends `SIGTERM`, and clears the pidfile.
79
+ If nothing is running it reports `not_running` gracefully and cleans up any stale
80
+ pidfile.
81
+
82
+ ```bash
83
+ pm web stop # stops the server on the default port
84
+ pm web stop --port 8080 --json
85
+ ```
86
+
87
+ #### `pm web doctor`
88
+
89
+ Runs preflight checks before starting the server: Node version (>= 20), whether
90
+ runtime dependencies (express, etc.) are installed, whether the target port is
91
+ free, whether `pm` is on `PATH`, and whether the workspace is initialized.
92
+ Returns an overall `ok` boolean. The `port_available` check is informational (a
93
+ busy port may just be a server you already started) and does not gate `ok`.
94
+
95
+ ```bash
96
+ pm web doctor
97
+ pm web doctor --json
98
+ ```
99
+
100
+ > Note: the `services` extension capability is intentionally **not** declared.
101
+ > The pm SDK's `registerService` only overrides one of eight fixed core services
102
+ > (e.g. `output_format`), which would alter core output for unrelated commands;
103
+ > the server lifecycle is exposed safely through the commands above instead.
57
104
 
58
105
  ### Environment Variables
59
106
 
@@ -64,6 +111,7 @@ The package repository is at **github.com/unbraind/pm-web**.
64
111
  | `PM_WEB_SECRET_KEY` | Recommended | At-rest encryption key for saved GitHub PATs. Falls back to `JWT_SECRET`; use at least 32 characters |
65
112
  | `PM_WEB_BOOTSTRAP_ADMIN_EMAIL` | Recommended | Email of the user account to auto-promote to admin on schema init. Leave unset to skip auto-promotion (manage admins via the admin UI). |
66
113
  | `PORT` | No | Server port (default: 4000) |
114
+ | `PM_WEB_STATE_DIR` | No | Directory for the `--detach` pidfile used by `pm web stop` (default: OS temp dir) |
67
115
  | `NODE_ENV` | No | `production` enables caching |
68
116
  | `OLLAMA_BASE_URL` / `OLLAMA_HOST` | No | Local Ollama endpoint for semantic pm search |
69
117
  | `PM_OLLAMA_MODEL` | No | Embedding model for new projects, default `qwen3-embedding:0.6b` |
@@ -118,6 +166,49 @@ extensions):
118
166
  search over id, title, tags and body.
119
167
  - `GET /api/projects/:projectId/pm/schema` — runtime types/statuses (existing).
120
168
  - `GET /api/projects/:projectId/pm/graph` — dependency graph (existing).
169
+ - `GET /api/projects/:projectId/pm/calendar.ics` — RFC 5545 iCalendar feed of
170
+ item deadlines (see below).
121
171
 
122
172
  The pure grouping/search helpers live in `src/board.ts` and are unit-tested
123
173
  independently of the database.
174
+
175
+ ## UI features
176
+
177
+ ### Theme (dark / light / auto)
178
+
179
+ The web UI supports three themes — **dark** (default), **light**, and **auto**
180
+ (follows the OS `prefers-color-scheme`). Toggle with the button in the top nav
181
+ or the `t` keyboard shortcut; the choice is persisted to `localStorage`. The
182
+ palette is driven entirely by CSS variables (`public/styles.css`); the toggle
183
+ logic lives in `public/src/theme.ts`.
184
+
185
+ ### Shareable filtered views
186
+
187
+ The Items view mirrors its filters into the URL query string, so a filtered
188
+ view is shareable and bookmarkable:
189
+
190
+ ```
191
+ /items?status=open&type=Feature&priority=1&assignee=alice&tag=release
192
+ ```
193
+
194
+ Filterable dimensions: `status`, `type`, `priority`, `sprint`, `release`,
195
+ `assignee`, `tag`. Opening such a URL restores the filters; use the **Copy
196
+ link** button to copy the current filtered view. The pure (de)serialization
197
+ helpers live in `public/src/filters.ts` and are unit-tested.
198
+
199
+ ### Calendar export (iCal / .ics)
200
+
201
+ `GET /api/projects/:projectId/pm/calendar.ics` returns an RFC 5545 `VCALENDAR`
202
+ with a `VEVENT` per item that has a deadline, suitable for **subscribing** in
203
+ Google Calendar, Outlook, or Apple Calendar. Date-only deadlines become all-day
204
+ events; items without a deadline are skipped. Use the **Export .ics** button on
205
+ the Calendar view to download, or subscribe to the feed URL. Because calendar
206
+ clients cannot send cookies, the feed also accepts the JWT via a `?token=<jwt>`
207
+ query parameter (in addition to the usual `Authorization: Bearer` header /
208
+ cookie). The pure generator lives in `src/ical.ts` and is unit-tested.
209
+
210
+ ### Keyboard shortcuts
211
+
212
+ Press `?` for the in-app shortcuts overlay. Highlights: `Ctrl/⌘+K` global
213
+ search, `/` focus search, `n`/`c` new item, `t` cycle theme, `a` activity,
214
+ `g i` items, `g g` graph, `g s` search, `g c` calendar, `Esc` close modal.
package/dist/auth.js CHANGED
@@ -15,6 +15,12 @@ export function extractToken(req) {
15
15
  const cookie = req.cookies?.pm_token;
16
16
  if (cookie)
17
17
  return cookie;
18
+ // Allow a `?token=` query param. Calendar clients subscribing to an .ics
19
+ // feed cannot send cookies or an Authorization header, so the feed URL
20
+ // carries the JWT directly. Additive — header/cookie still take precedence.
21
+ const queryToken = req.query?.["token"];
22
+ if (typeof queryToken === "string" && queryToken)
23
+ return queryToken;
18
24
  return null;
19
25
  }
20
26
  //# sourceMappingURL=auth.js.map
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAG/B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,kCAAkC,CAAC;AAChF,MAAM,WAAW,GAAG,KAAK,CAAC;AAO1B,MAAM,UAAU,SAAS,CAAC,OAAmB;IAC3C,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAe,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IAC7C,IAAI,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,MAAM,GAAI,GAAsD,CAAC,OAAO,EAAE,QAAQ,CAAC;IACzF,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAG/B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,kCAAkC,CAAC;AAChF,MAAM,WAAW,GAAG,KAAK,CAAC;AAO1B,MAAM,UAAU,SAAS,CAAC,OAAmB;IAC3C,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAe,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IAC7C,IAAI,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,MAAM,GAAI,GAAsD,CAAC,OAAO,EAAE,QAAQ,CAAC;IACzF,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,yEAAyE;IACzE,uEAAuE;IACvE,4EAA4E;IAC5E,MAAM,UAAU,GAAI,GAAG,CAAC,KAA6C,EAAE,CAAC,OAAO,CAAC,CAAC;IACjF,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IACpE,OAAO,IAAI,CAAC;AACd,CAAC"}
package/dist/board.js CHANGED
@@ -1,20 +1,39 @@
1
1
  // Pure, dependency-free helpers for the kanban board and search endpoints.
2
2
  // Kept in their own module (no db/neo4j imports) so they are unit-testable
3
3
  // without booting the server or a database.
4
+ export function normalizeStatusKey(value) {
5
+ const raw = typeof value === "string" && value.trim() ? value.trim() : "open";
6
+ return raw.toLowerCase().replace(/[\s-]+/g, "_");
7
+ }
8
+ export function normalizeItemTags(value) {
9
+ if (Array.isArray(value))
10
+ return value.map(String).map((tag) => tag.trim()).filter(Boolean);
11
+ if (typeof value === "string")
12
+ return value.split(",").map((tag) => tag.trim()).filter(Boolean);
13
+ return [];
14
+ }
4
15
  // Group items into board columns keyed by the workspace's actual statuses
5
16
  // (from `pm contracts`), so the kanban board reflects whatever statuses the
6
17
  // installed pm CLI + extensions define. Items with an unlisted status fall into
7
18
  // a trailing "(other)" column so nothing is silently dropped.
8
19
  export function boardColumns(items, statuses) {
9
- const known = statuses.length > 0 ? statuses : ["open", "in_progress", "blocked", "closed"];
20
+ const known = (statuses.length > 0 ? statuses : ["open", "in_progress", "blocked", "closed"])
21
+ .map((status) => String(status).trim())
22
+ .filter(Boolean);
10
23
  const columns = new Map();
11
- for (const s of known)
12
- columns.set(s, []);
24
+ const statusKeyToColumn = new Map();
25
+ for (const s of known) {
26
+ const key = normalizeStatusKey(s);
27
+ if (!statusKeyToColumn.has(key)) {
28
+ statusKeyToColumn.set(key, s);
29
+ columns.set(s, []);
30
+ }
31
+ }
13
32
  const OTHER = "(other)";
14
33
  for (const item of items) {
15
- const s = item.status ?? "open";
16
- if (columns.has(s))
17
- columns.get(s).push(item);
34
+ const column = statusKeyToColumn.get(normalizeStatusKey(item.status));
35
+ if (column && columns.has(column))
36
+ columns.get(column).push(item);
18
37
  else {
19
38
  if (!columns.has(OTHER))
20
39
  columns.set(OTHER, []);
@@ -32,8 +51,9 @@ export function filterItemsByQuery(items, query) {
32
51
  const hay = [
33
52
  item.id,
34
53
  item.title ?? "",
35
- (item.tags ?? []).join(" "),
54
+ normalizeItemTags(item.tags).join(" "),
36
55
  item.body ?? "",
56
+ item.description ?? "",
37
57
  ].join(" ").toLowerCase();
38
58
  return hay.includes(q);
39
59
  });
package/dist/board.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,4CAA4C;AAU5C,0EAA0E;AAC1E,4EAA4E;AAC5E,gFAAgF;AAChF,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAC1B,KAAU,EACV,QAAkB;IAElB,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAG,IAAI,GAAG,EAAe,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,SAAS,CAAC;IACxB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC;QAChC,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAC1C,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,kBAAkB,CAAsB,KAAU,EAAE,KAAa;IAC/E,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAC3B,MAAM,GAAG,GAAG;YACV,IAAI,CAAC,EAAE;YACP,IAAI,CAAC,KAAK,IAAI,EAAE;YAChB,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YAC3B,IAAI,CAAC,IAAI,IAAI,EAAE;SAChB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"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"}
package/dist/ical.js ADDED
@@ -0,0 +1,141 @@
1
+ // Pure, dependency-free RFC 5545 (iCalendar) generation for pm items.
2
+ //
3
+ // Kept in its own module (no db/express/neo4j imports) so it is unit-testable
4
+ // without booting the server or a database. The pm route layer reads items via
5
+ // the pm CLI and hands the relevant fields here.
6
+ // Escape a text value per RFC 5545 §3.3.11 (TEXT): backslash, semicolon,
7
+ // comma and newlines must be escaped.
8
+ export function icsEscapeText(value) {
9
+ return value
10
+ .replace(/\\/g, "\\\\")
11
+ .replace(/;/g, "\\;")
12
+ .replace(/,/g, "\\,")
13
+ .replace(/\r\n|\r|\n/g, "\\n");
14
+ }
15
+ // Fold long content lines to 75 octets per RFC 5545 §3.1. Continuation lines
16
+ // begin with a single space. We fold on character boundaries which is safe for
17
+ // the ASCII-dominant content pm produces.
18
+ export function foldLine(line) {
19
+ if (line.length <= 75)
20
+ return line;
21
+ const parts = [];
22
+ let remaining = line;
23
+ parts.push(remaining.slice(0, 75));
24
+ remaining = remaining.slice(75);
25
+ while (remaining.length > 0) {
26
+ // 74 chars + 1 leading space = 75 octets per continuation line.
27
+ parts.push(" " + remaining.slice(0, 74));
28
+ remaining = remaining.slice(74);
29
+ }
30
+ return parts.join("\r\n");
31
+ }
32
+ // Format a Date as a UTC timestamp: YYYYMMDDTHHMMSSZ (RFC 5545 form 2).
33
+ export function formatUtcTimestamp(d) {
34
+ const p = (n, w = 2) => String(n).padStart(w, "0");
35
+ return (`${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}` +
36
+ `T${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}Z`);
37
+ }
38
+ // Format a Date as an all-day DATE value: YYYYMMDD (RFC 5545 §3.3.4).
39
+ export function formatDateValue(d) {
40
+ const p = (n) => String(n).padStart(2, "0");
41
+ return `${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}`;
42
+ }
43
+ // A pm deadline may be a date-only string (YYYY-MM-DD) or a full ISO datetime.
44
+ // Returns null for anything unparseable so callers can skip the item.
45
+ function parseDeadline(raw) {
46
+ const trimmed = raw.trim();
47
+ if (!trimmed)
48
+ return null;
49
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(trimmed);
50
+ const d = new Date(dateOnly ? `${trimmed}T00:00:00Z` : trimmed);
51
+ if (Number.isNaN(d.getTime()))
52
+ return null;
53
+ // pm normalizes date-only deadlines to a midnight-UTC ISO timestamp
54
+ // (e.g. "2026-06-10T00:00:00.000Z"). Treat any exact-midnight-UTC value as
55
+ // an all-day event, which renders far better in calendar clients than a
56
+ // zero-length event at 00:00.
57
+ const isMidnightUtc = d.getUTCHours() === 0 &&
58
+ d.getUTCMinutes() === 0 &&
59
+ d.getUTCSeconds() === 0 &&
60
+ d.getUTCMilliseconds() === 0;
61
+ return { date: d, allDay: dateOnly || isMidnightUtc };
62
+ }
63
+ // Build a single VEVENT for a pm item with a deadline. Returns null when the
64
+ // item has no usable deadline.
65
+ export function itemToVevent(item, opts) {
66
+ if (!item.deadline)
67
+ return null;
68
+ const parsed = parseDeadline(item.deadline);
69
+ if (!parsed)
70
+ return null;
71
+ const lines = ["BEGIN:VEVENT"];
72
+ lines.push(`UID:${item.id}@${opts.uidDomain}`);
73
+ lines.push(`DTSTAMP:${opts.dtstamp}`);
74
+ if (parsed.allDay) {
75
+ // All-day event: DTSTART/DTEND use VALUE=DATE, DTEND is the next day
76
+ // (exclusive end) per RFC 5545.
77
+ const end = new Date(parsed.date.getTime() + 24 * 60 * 60 * 1000);
78
+ lines.push(`DTSTART;VALUE=DATE:${formatDateValue(parsed.date)}`);
79
+ lines.push(`DTEND;VALUE=DATE:${formatDateValue(end)}`);
80
+ }
81
+ else {
82
+ lines.push(`DTSTART:${formatUtcTimestamp(parsed.date)}`);
83
+ lines.push(`DTEND:${formatUtcTimestamp(parsed.date)}`);
84
+ }
85
+ const summaryPrefix = item.type ? `[${item.type}] ` : "";
86
+ lines.push(`SUMMARY:${icsEscapeText(summaryPrefix + (item.title ?? item.id))}`);
87
+ const descParts = [`pm item ${item.id}`];
88
+ if (item.status)
89
+ descParts.push(`Status: ${item.status}`);
90
+ if (item.assignee)
91
+ descParts.push(`Assignee: ${item.assignee}`);
92
+ if (item.priority !== undefined && item.priority !== null)
93
+ descParts.push(`Priority: P${item.priority}`);
94
+ if (item.tags && item.tags.length)
95
+ descParts.push(`Tags: ${item.tags.join(", ")}`);
96
+ // Join with real newlines; icsEscapeText turns them into the RFC 5545 "\n"
97
+ // escape sequence (escaping here directly would double-escape).
98
+ lines.push(`DESCRIPTION:${icsEscapeText(descParts.join("\n"))}`);
99
+ // Map pm priority (0 highest .. 4 lowest) onto the iCal 1..9 scale where 1
100
+ // is highest. Leave unset when no priority is present.
101
+ if (typeof item.priority === "number" && item.priority >= 0 && item.priority <= 4) {
102
+ const icalPriority = Math.min(9, Math.max(1, item.priority * 2 + 1));
103
+ lines.push(`PRIORITY:${icalPriority}`);
104
+ }
105
+ if (item.tags && item.tags.length) {
106
+ lines.push(`CATEGORIES:${item.tags.map(icsEscapeText).join(",")}`);
107
+ }
108
+ // Closed/canceled items are recorded as CANCELLED so subscribers can hide
109
+ // them; everything else is CONFIRMED.
110
+ const cancelled = item.status === "closed" || item.status === "canceled";
111
+ lines.push(`STATUS:${cancelled ? "CANCELLED" : "CONFIRMED"}`);
112
+ lines.push("END:VEVENT");
113
+ return lines.map(foldLine).join("\r\n");
114
+ }
115
+ // Build a complete VCALENDAR document from pm items. Items without a usable
116
+ // deadline are skipped. Output uses CRLF line endings per RFC 5545 §3.1.
117
+ export function buildIcsCalendar(items, opts = {}) {
118
+ const now = opts.now ?? new Date();
119
+ const dtstamp = formatUtcTimestamp(now);
120
+ const uidDomain = (opts.uidDomain ?? "pm-web").replace(/[^A-Za-z0-9._-]/g, "-");
121
+ const calName = opts.calendarName ?? "pm-web";
122
+ const head = [
123
+ "BEGIN:VCALENDAR",
124
+ "VERSION:2.0",
125
+ "PRODID:-//pm-web//pm-cli calendar//EN",
126
+ "CALSCALE:GREGORIAN",
127
+ "METHOD:PUBLISH",
128
+ `X-WR-CALNAME:${icsEscapeText(calName)}`,
129
+ `NAME:${icsEscapeText(calName)}`,
130
+ ];
131
+ const events = [];
132
+ for (const item of items) {
133
+ const vevent = itemToVevent(item, { uidDomain, dtstamp });
134
+ if (vevent)
135
+ events.push(vevent);
136
+ }
137
+ const all = [...head.map(foldLine), ...events, "END:VCALENDAR"];
138
+ // Trailing CRLF is conventional and accepted by all major parsers.
139
+ return all.join("\r\n") + "\r\n";
140
+ }
141
+ //# sourceMappingURL=ical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ical.js","sourceRoot":"","sources":["../src/ical.ts"],"names":[],"mappings":"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"}