@unbrained/pm-web 2026.7.27 → 2026.7.29

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 (108) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +1 -2
  3. package/dist/app.js +2 -0
  4. package/dist/app.js.map +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/oidc.js.map +1 -1
  7. package/dist/routes/extensions.d.ts +16 -0
  8. package/dist/routes/extensions.js +214 -0
  9. package/dist/routes/extensions.js.map +1 -0
  10. package/dist/routes/oidc.d.ts +1 -1
  11. package/dist/routes/pm.js +125 -0
  12. package/dist/routes/pm.js.map +1 -1
  13. package/dist/services/mutation-event-watcher.d.ts +1 -1
  14. package/dist/services/package-catalog.d.ts +108 -0
  15. package/dist/services/package-catalog.js +256 -0
  16. package/dist/services/package-catalog.js.map +1 -0
  17. package/dist/services/pm-runner.d.ts +66 -1
  18. package/dist/services/pm-runner.js +83 -47
  19. package/dist/services/pm-runner.js.map +1 -1
  20. package/dist/services/project-watcher.d.ts +1 -1
  21. package/dist/services/realtime-bus.d.ts +1 -1
  22. package/manifest.json +1 -1
  23. package/package.json +24 -11
  24. package/public/index.html +4 -0
  25. package/public/src/api-types.js +17 -0
  26. package/public/src/api-types.js.map +1 -0
  27. package/public/src/api-types.ts +618 -0
  28. package/public/src/api.js +6 -3
  29. package/public/src/api.js.map +1 -1
  30. package/public/src/api.ts +30 -10
  31. package/public/src/app.js +11 -4
  32. package/public/src/app.js.map +1 -1
  33. package/public/src/app.ts +55 -12
  34. package/public/src/constants.js +1 -1
  35. package/public/src/constants.js.map +1 -1
  36. package/public/src/constants.ts +1 -1
  37. package/public/src/i18n/de.json +25 -1
  38. package/public/src/i18n/en.json +25 -1
  39. package/public/src/i18n/es.json +25 -1
  40. package/public/src/i18n/zh.json +26 -2
  41. package/public/src/i18n.ts +1 -1
  42. package/public/src/sw.ts +2 -0
  43. package/public/src/types.ts +11 -2
  44. package/public/src/views/activity.js +1 -1
  45. package/public/src/views/activity.js.map +1 -1
  46. package/public/src/views/activity.ts +5 -4
  47. package/public/src/views/calendar.js +1 -1
  48. package/public/src/views/calendar.js.map +1 -1
  49. package/public/src/views/calendar.ts +13 -11
  50. package/public/src/views/comments-audit.js.map +1 -1
  51. package/public/src/views/comments-audit.ts +7 -6
  52. package/public/src/views/config.js +1 -1
  53. package/public/src/views/config.js.map +1 -1
  54. package/public/src/views/config.ts +2 -2
  55. package/public/src/views/context.js +3 -3
  56. package/public/src/views/context.js.map +1 -1
  57. package/public/src/views/context.ts +10 -9
  58. package/public/src/views/create.js.map +1 -1
  59. package/public/src/views/create.ts +3 -2
  60. package/public/src/views/dedupe.js +5 -5
  61. package/public/src/views/dedupe.js.map +1 -1
  62. package/public/src/views/dedupe.ts +9 -8
  63. package/public/src/views/export.js.map +1 -1
  64. package/public/src/views/export.ts +2 -2
  65. package/public/src/views/github.js +5 -4
  66. package/public/src/views/github.js.map +1 -1
  67. package/public/src/views/github.ts +20 -18
  68. package/public/src/views/graph.js +7 -9
  69. package/public/src/views/graph.js.map +1 -1
  70. package/public/src/views/graph.ts +11 -13
  71. package/public/src/views/groups.js +1 -1
  72. package/public/src/views/groups.js.map +1 -1
  73. package/public/src/views/groups.ts +9 -8
  74. package/public/src/views/health.js +16 -2
  75. package/public/src/views/health.js.map +1 -1
  76. package/public/src/views/health.ts +26 -8
  77. package/public/src/views/items.js +18 -12
  78. package/public/src/views/items.js.map +1 -1
  79. package/public/src/views/items.ts +65 -35
  80. package/public/src/views/normalize.js.map +1 -1
  81. package/public/src/views/normalize.ts +5 -4
  82. package/public/src/views/packages.js +230 -0
  83. package/public/src/views/packages.js.map +1 -0
  84. package/public/src/views/packages.ts +255 -0
  85. package/public/src/views/plan.js +0 -2
  86. package/public/src/views/plan.js.map +1 -1
  87. package/public/src/views/plan.ts +5 -6
  88. package/public/src/views/projects.js.map +1 -1
  89. package/public/src/views/projects.ts +8 -7
  90. package/public/src/views/router.js +5 -0
  91. package/public/src/views/router.js.map +1 -1
  92. package/public/src/views/router.ts +3 -0
  93. package/public/src/views/search.js.map +1 -1
  94. package/public/src/views/search.ts +3 -2
  95. package/public/src/views/settings.js.map +1 -1
  96. package/public/src/views/settings.ts +8 -6
  97. package/public/src/views/shared.js.map +1 -1
  98. package/public/src/views/shared.ts +4 -3
  99. package/public/src/views/sharing.js.map +1 -1
  100. package/public/src/views/sharing.ts +5 -4
  101. package/public/src/views/stats.js +1 -1
  102. package/public/src/views/stats.js.map +1 -1
  103. package/public/src/views/stats.ts +10 -9
  104. package/public/src/views/templates.js.map +1 -1
  105. package/public/src/views/templates.ts +6 -5
  106. package/public/src/views/validate.js.map +1 -1
  107. package/public/src/views/validate.ts +6 -5
  108. package/public/sw.js +2 -0
@@ -12,6 +12,36 @@ import { loadItemsBadge } from './projects.js';
12
12
  import { renderLocalGraph, destroyLocalGraph } from './graph.js';
13
13
  import { EMPTY_FILTERS, filtersToQueryString, filtersFromSearchParams, hasActiveFilters } from '../filters.js';
14
14
  import type { Item } from '../types.js';
15
+ import type {
16
+ CloseManyResponse,
17
+ CommentsResponse,
18
+ DepsResponse,
19
+ FilesResponse,
20
+ HistoryResponse,
21
+ ItemResponse,
22
+ LearningsResponse,
23
+ ListResponse,
24
+ ItemPlan,
25
+ NotesResponse,
26
+ TestsResponse,
27
+ UpdateManyResponse,
28
+ } from '../api-types.js';
29
+
30
+ // Stash the bulk-update / bulk-close payloads on the apply buttons without
31
+ // mutating the DOM nodes. A module-scoped WeakMap per modal keeps the payload
32
+ // alive across the Preview → Apply call pair and is GC-friendly.
33
+ const bulkUpdatePayloads = new WeakMap<HTMLElement, Record<string, string>>();
34
+ const bulkClosePayloads = new WeakMap<HTMLElement, BulkClosePayload>();
35
+
36
+ /** Payload stashed for `applyBulkClose` by `previewBulkClose`. */
37
+ interface BulkClosePayload {
38
+ fStatus: string;
39
+ fType: string;
40
+ fSprint: string;
41
+ fAssignee: string;
42
+ targetStatus: string;
43
+ reason: string;
44
+ }
15
45
 
16
46
  // Mirror the active item filters into the URL query string (on the /items
17
47
  // path) so a filtered view is shareable and bookmarkable. Replaces the current
@@ -215,9 +245,9 @@ export async function previewBulkUpdate(): Promise<void> {
215
245
  payload.dryRun = 'true';
216
246
 
217
247
  try {
218
- const data = await api('POST', `/projects/${pid}/pm/update-many`, payload);
219
- const matched: any[] = (data as any).item_plans || (data as any).items || (data as any).matched || [];
220
- const count = (data as any).matched_count ?? (data as any).count ?? (data as any).total ?? matched.length;
248
+ const data = await api<UpdateManyResponse>('POST', `/projects/${pid}/pm/update-many`, payload);
249
+ const matched: ItemPlan[] = data.item_plans ?? [];
250
+ const count = data.matched_count ?? matched.length;
221
251
  const applyBtn = document.getElementById('bu-apply-btn') as HTMLButtonElement | null;
222
252
  if (previewEl) {
223
253
  const updateParts: string[] = [];
@@ -236,14 +266,14 @@ export async function previewBulkUpdate(): Promise<void> {
236
266
  previewEl.innerHTML = `
237
267
  <div style="background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.25);border-radius:var(--radius);padding:10px 14px;font-size:13px">
238
268
  <div style="margin-bottom:8px">Will update <strong>${displayCount}</strong> item${displayCount!==1?'s':''}: ${updateDesc}</div>
239
- ${matched.slice(0,8).map((it: any)=>`<div style="color:var(--text-secondary);font-size:12px">· ${escHtml(it.id||'')} ${escHtml(it.title||'')}</div>`).join('')}
269
+ ${matched.slice(0,8).map((it)=>`<div style="color:var(--text-secondary);font-size:12px">· ${escHtml(it.id)}${it.changes?.length ? ` — ${escHtml(it.changes.map((c) => `${c.field}: ${String(c.before)} → ${String(c.after)}`).join(', '))}` : ''}</div>`).join('')}
240
270
  ${displayCount > 8 ? `<div style="color:var(--text-muted);font-size:12px;margin-top:4px">… and ${displayCount - 8} more</div>` : ''}
241
271
  </div>`;
242
272
  if (applyBtn) applyBtn.disabled = false;
243
273
  // Store payload for apply (without dryRun)
244
274
  const applyPayload = { ...payload };
245
275
  delete applyPayload.dryRun;
246
- (applyBtn as any)._bulkPayload = applyPayload;
276
+ if (applyBtn) bulkUpdatePayloads.set(applyBtn, applyPayload);
247
277
  }
248
278
  }
249
279
  } catch(err: unknown) {
@@ -255,13 +285,13 @@ export async function applyBulkUpdate(): Promise<void> {
255
285
  const pid = state.currentProject?.id;
256
286
  if (!pid) return;
257
287
  const applyBtn = document.getElementById('bu-apply-btn') as HTMLButtonElement | null;
258
- const payload = (applyBtn as any)?._bulkPayload;
288
+ const payload = applyBtn ? bulkUpdatePayloads.get(applyBtn) : undefined;
259
289
  if (!payload) { toast('Run Preview first', 'info'); return; }
260
290
  if (applyBtn) { applyBtn.disabled = true; applyBtn.textContent = 'Applying…'; }
261
291
  try {
262
- const data = await api('POST', `/projects/${pid}/pm/update-many`, payload);
263
- const updated = (data as any).updated_count ?? (data as any).updated ?? (data as any).count ?? (data as any).total ?? 'some';
264
- const failed = (data as any).failed_count ?? 0;
292
+ const data = await api<UpdateManyResponse>('POST', `/projects/${pid}/pm/update-many`, payload);
293
+ const updated = data.updated_count ?? 'some';
294
+ const failed = data.failed_count ?? 0;
265
295
  if (failed > 0) {
266
296
  toast(`Updated ${updated} item${updated!==1?'s':''} (${failed} failed)`, 'info');
267
297
  } else {
@@ -366,9 +396,9 @@ export async function previewBulkClose(): Promise<void> {
366
396
  if (fType) previewPayload.filterType = fType;
367
397
  if (fSprint) previewPayload.filterSprint = fSprint;
368
398
  if (fAssignee) previewPayload.filterAssignee = fAssignee;
369
- const data = await api('POST', `/projects/${pid}/pm/update-many`, previewPayload);
370
- const matched: any[] = (data as any).item_plans || (data as any).items || (data as any).matched || [];
371
- const count = (data as any).matched_count ?? (data as any).count ?? (data as any).total ?? matched.length;
399
+ const data = await api<UpdateManyResponse>('POST', `/projects/${pid}/pm/update-many`, previewPayload);
400
+ const matched: ItemPlan[] = data.item_plans ?? [];
401
+ const count = data.matched_count ?? matched.length;
372
402
  const applyBtn = document.getElementById('bc-apply-btn') as HTMLButtonElement | null;
373
403
  if (previewEl) {
374
404
  if (count === 0 && matched.length === 0) {
@@ -379,12 +409,12 @@ export async function previewBulkClose(): Promise<void> {
379
409
  previewEl.innerHTML = `
380
410
  <div style="background:rgba(248,113,113,0.08);border:1px solid rgba(248,113,113,0.25);border-radius:var(--radius);padding:10px 14px;font-size:13px">
381
411
  <div style="margin-bottom:8px">Will <strong>${targetStatus}</strong> <strong>${displayCount}</strong> item${displayCount!==1?'s':''}. Reason: <em>${escHtml(reason)}</em></div>
382
- ${matched.slice(0,8).map((it: any)=>`<div style="color:var(--text-secondary);font-size:12px">· ${escHtml(it.id||'')} ${escHtml(it.title||'')}</div>`).join('')}
412
+ ${matched.slice(0,8).map((it)=>`<div style="color:var(--text-secondary);font-size:12px">· ${escHtml(it.id)}${it.changes?.length ? ` — ${escHtml(it.changes.map((c) => `${c.field}: ${String(c.before)} → ${String(c.after)}`).join(', '))}` : ''}</div>`).join('')}
383
413
  ${displayCount > 8 ? `<div style="color:var(--text-muted);font-size:12px;margin-top:4px">… and ${displayCount - 8} more</div>` : ''}
384
414
  </div>`;
385
415
  if (applyBtn) {
386
416
  applyBtn.disabled = false;
387
- (applyBtn as any)._bulkClosePayload = { fStatus, fType, fSprint, fAssignee, targetStatus, reason };
417
+ bulkClosePayloads.set(applyBtn, { fStatus, fType, fSprint, fAssignee, targetStatus, reason });
388
418
  }
389
419
  }
390
420
  }
@@ -397,7 +427,7 @@ export async function applyBulkClose(): Promise<void> {
397
427
  const pid = state.currentProject?.id;
398
428
  if (!pid) return;
399
429
  const applyBtn = document.getElementById('bc-apply-btn') as HTMLButtonElement | null;
400
- const bulkClosePayload = (applyBtn as any)?._bulkClosePayload as Record<string, string> | undefined;
430
+ const bulkClosePayload = applyBtn ? bulkClosePayloads.get(applyBtn) : undefined;
401
431
  if (!bulkClosePayload) { toast('Run Preview first', 'info'); return; }
402
432
  if (applyBtn) { applyBtn.disabled = true; applyBtn.textContent = 'Closing…'; }
403
433
 
@@ -413,9 +443,9 @@ export async function applyBulkClose(): Promise<void> {
413
443
  if (bulkClosePayload.fAssignee) payload.filterAssignee = bulkClosePayload.fAssignee;
414
444
 
415
445
  try {
416
- const data = await api('POST', `/projects/${pid}/pm/close-many`, payload);
417
- const closed = (data as any).closed_count ?? 'some';
418
- const failed = (data as any).failed_count ?? 0;
446
+ const data = await api<CloseManyResponse>('POST', `/projects/${pid}/pm/close-many`, payload);
447
+ const closed = data.closed_count ?? 'some';
448
+ const failed = data.failed_count ?? 0;
419
449
  if (failed > 0) {
420
450
  toast(`Closed ${closed} item${closed!==1?'s':''} (${failed} failed)`, 'info');
421
451
  } else {
@@ -497,7 +527,7 @@ export async function fetchAndRenderItems(): Promise<void> {
497
527
  const endpoint = f.status ? `list?${params}` : `list-all?${params}`;
498
528
 
499
529
  try {
500
- const data = await api('GET',`/projects/${pid}/pm/${endpoint}`);
530
+ const data = await api<ListResponse>('GET',`/projects/${pid}/pm/${endpoint}`);
501
531
  let items: Item[] = data.items || [];
502
532
  // Tag is filtered client-side (pm list has no --tag flag): match items
503
533
  // whose tag list contains the requested tag (case-insensitive).
@@ -603,17 +633,17 @@ export async function openItemDetail(itemId: string): Promise<void> {
603
633
 
604
634
  try {
605
635
  const [itemData, commentsData, historyData, depsData, learningsData, notesData, testsData, filesData] = await Promise.all([
606
- api('GET',`/projects/${pid}/pm/get/${itemId}`),
607
- api('GET',`/projects/${pid}/pm/comments/${itemId}`).catch(()=>({comments:[]})),
608
- api('GET',`/projects/${pid}/pm/history/${itemId}`).catch(()=>({history:[]})),
609
- api('GET',`/projects/${pid}/pm/deps/${itemId}`).catch(()=>({deps:[]})),
610
- api('GET',`/projects/${pid}/pm/learnings/${itemId}`).catch(()=>({learnings:[]})),
611
- api('GET',`/projects/${pid}/pm/notes/${itemId}`).catch(()=>({notes:[]})),
612
- api('GET',`/projects/${pid}/pm/tests/${itemId}`).catch(()=>({tests:[]})),
613
- api('GET',`/projects/${pid}/pm/files/${itemId}`).catch(()=>({files:[]})),
636
+ api<ItemResponse>('GET',`/projects/${pid}/pm/get/${itemId}`),
637
+ api<CommentsResponse>('GET',`/projects/${pid}/pm/comments/${itemId}`).catch(()=>({comments:[]})),
638
+ api<HistoryResponse>('GET',`/projects/${pid}/pm/history/${itemId}`).catch(()=>({history:[]})),
639
+ api<DepsResponse>('GET',`/projects/${pid}/pm/deps/${itemId}`).catch(()=>({deps:[]} as DepsResponse)),
640
+ api<LearningsResponse>('GET',`/projects/${pid}/pm/learnings/${itemId}`).catch(()=>({learnings:[]})),
641
+ api<NotesResponse>('GET',`/projects/${pid}/pm/notes/${itemId}`).catch(()=>({notes:[]})),
642
+ api<TestsResponse>('GET',`/projects/${pid}/pm/tests/${itemId}`).catch(()=>({tests:[]})),
643
+ api<FilesResponse>('GET',`/projects/${pid}/pm/files/${itemId}`).catch(()=>({files:[]})),
614
644
  ]);
615
645
 
616
- const item = itemData.item || itemData;
646
+ const item: Item = itemData.item || (itemData as Item);
617
647
  const comments = commentsData.comments || [];
618
648
  const history = historyData.history || [];
619
649
  const deps = depsData.deps || depsData.dependencies || [];
@@ -632,7 +662,7 @@ export async function openItemDetail(itemId: string): Promise<void> {
632
662
 
633
663
  const notesHtml = notes.length === 0
634
664
  ? '<div style="color:var(--text-muted);font-size:13px;margin-bottom:12px">No notes yet</div>'
635
- : notes.map((n: any)=>`
665
+ : notes.map((n)=>`
636
666
  <div class="notes-item">
637
667
  <div class="notes-item-meta">${relTime(n.timestamp||n.created_at||'')}</div>
638
668
  <div class="notes-item-text">${escHtml(n.text||n.content||JSON.stringify(n))}</div>
@@ -640,7 +670,7 @@ export async function openItemDetail(itemId: string): Promise<void> {
640
670
 
641
671
  const testsHtml = tests.length === 0
642
672
  ? '<div style="color:var(--text-muted);font-size:13px;margin-bottom:12px">No tests defined</div>'
643
- : tests.map((t: any)=>`
673
+ : tests.map((t)=>`
644
674
  <div class="test-item">
645
675
  <div style="flex:1">
646
676
  <div class="test-item-cmd">${escHtml(t.command||t.cmd||JSON.stringify(t))}</div>
@@ -650,7 +680,7 @@ export async function openItemDetail(itemId: string): Promise<void> {
650
680
 
651
681
  const commentsHtml = comments.length === 0
652
682
  ? '<div style="color:var(--text-muted);font-size:13px">No comments yet</div>'
653
- : comments.map((c: any)=>`
683
+ : comments.map((c)=>`
654
684
  <div class="comment-item">
655
685
  <div class="comment-avatar">💬</div>
656
686
  <div class="comment-body">
@@ -661,7 +691,7 @@ export async function openItemDetail(itemId: string): Promise<void> {
661
691
 
662
692
  const historyHtml = history.length === 0
663
693
  ? '<div style="color:var(--text-muted);font-size:13px">No history</div>'
664
- : history.slice(0,10).map((h: any)=>`
694
+ : history.slice(0,10).map((h)=>`
665
695
  <div class="history-item">
666
696
  <div class="history-dot"></div>
667
697
  <div><div class="history-text">${escHtml(h.message||h.action||JSON.stringify(h))}</div><div class="history-time">${relTime(h.timestamp||h.created_at)}</div></div>
@@ -675,7 +705,7 @@ export async function openItemDetail(itemId: string): Promise<void> {
675
705
  <div class="item-detail-title">${escHtml(item.title)}</div>
676
706
  <div class="item-detail-meta">
677
707
  ${statusBadge(item.status||'draft')}
678
- <div class="meta-chip">${priorityDot(item.priority??4)} <strong>P${item.priority}</strong> ${PRIORITY_LABELS[item.priority]||''}</div>
708
+ <div class="meta-chip">${priorityDot(item.priority??4)} <strong>P${item.priority ?? '—'}</strong> ${item.priority !== undefined ? (PRIORITY_LABELS[item.priority]||'') : ''}</div>
679
709
  ${item.created_at ? `<div class="meta-chip">Created <strong>${fmtDate(item.created_at)}</strong></div>` : ''}
680
710
  ${item.updated_at ? `<div class="meta-chip">Updated <strong>${relTime(item.updated_at)}</strong></div>` : ''}
681
711
  ${item.parent ? `<div class="meta-chip">Parent <strong class="mono">${escHtml(item.parent)}</strong></div>` : ''}
@@ -854,7 +884,7 @@ export async function openItemDetail(itemId: string): Promise<void> {
854
884
  <div style="margin-bottom:16px">
855
885
  ${learnings.length === 0
856
886
  ? `<div style="color:var(--text-muted);font-size:13px;margin-bottom:12px">No learnings recorded</div>`
857
- : learnings.map((l: any)=>`<div class="learning-row">
887
+ : learnings.map((l)=>`<div class="learning-row">
858
888
  <div style="font-size:11px;color:var(--text-muted);margin-bottom:3px">${relTime(l.timestamp||l.created_at||'')}</div>
859
889
  ${escHtml(l.text||l.content||JSON.stringify(l))}
860
890
  </div>`).join('')
@@ -900,7 +930,7 @@ export async function openItemDetail(itemId: string): Promise<void> {
900
930
  <div style="margin-bottom:16px">
901
931
  ${files.length === 0
902
932
  ? '<div style="color:var(--text-muted);font-size:13px;margin-bottom:12px">No files linked</div>'
903
- : files.map((f: any) => `
933
+ : files.map((f) => `
904
934
  <div class="file-row">
905
935
  <span style="color:var(--text-muted)">📄</span>
906
936
  <span class="file-path">${escHtml(f.path || f.name || JSON.stringify(f))}</span>
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.js","sourceRoot":"","sources":["normalize.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,iBAAiB;AACjB,kEAAkE;AAClE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAE/C,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,MAAM,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;IACxD,IAAI,CAAC,EAAE;QAAE,OAAO;IAChB,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAAC,EAAE,CAAC,SAAS,GAAG,wFAAwF,CAAC;QAAC,OAAO;IAAC,CAAC;IAC/I,EAAE,CAAC,SAAS,GAAG;;oHAEmG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC;;;2GAG3C,CAAC;IAC1G,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,aAAa,KAAK,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC,CAAC;QACpF,MAAM,IAAI,GAAI,IAAY,CAAC,IAAI,IAAK,IAAY,CAAC,aAAa,IAAI,IAAI,CAAC;QACvE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QACzD,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,SAAS,GAAG,mKAAmK,CAAC;QACtL,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,GAAG,CAAC,SAAS,GAAG;;gFAE0D,KAAK,CAAC,MAAM;;cAE9E,KAAK,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC;;;;gDAIY,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;oBACpF,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,wHAAwH,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,gDAAgD,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE;;qBAEzN,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;;;;eAMhB,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,SAAS,GAAG,yHAAyH,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,oBAAoB,CAAC;QACtM,CAAC;IACH,CAAC;IAAC,OAAM,GAAY,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QACzD,IAAI,GAAG;YAAE,GAAG,CAAC,SAAS,GAAG,iEAAiE,OAAO,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC;IACpK,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,KAAK,CAAC,cAAc;QAAE,OAAO;IAClC,KAAK,CAAC,wEAAwE,EAAE,MAAM,CAAC,CAAC;AAC1F,CAAC"}
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["normalize.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,iBAAiB;AACjB,kEAAkE;AAClE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAG/C,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,MAAM,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;IACxD,IAAI,CAAC,EAAE;QAAE,OAAO;IAChB,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAAC,EAAE,CAAC,SAAS,GAAG,wFAAwF,CAAC;QAAC,OAAO;IAAC,CAAC;IAC/I,EAAE,CAAC,SAAS,GAAG;;oHAEmG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC;;;2GAG3C,CAAC;IAC1G,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAoB,MAAM,EAAE,aAAa,KAAK,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC,CAAC;QACvG,MAAM,IAAI,GAAkB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC;QACpE,MAAM,KAAK,GAAsB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QAClE,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QACzD,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,SAAS,GAAG,mKAAmK,CAAC;QACtL,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,GAAG,CAAC,SAAS,GAAG;;gFAE0D,KAAK,CAAC,MAAM;;cAE9E,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;;;;gDAIiB,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;oBACpF,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,wHAAwH,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,gDAAgD,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE;;qBAEzN,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;;;;eAMhB,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,SAAS,GAAG,yHAAyH,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,oBAAoB,CAAC;QACtM,CAAC;IACH,CAAC;IAAC,OAAM,GAAY,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QACzD,IAAI,GAAG;YAAE,GAAG,CAAC,SAAS,GAAG,iEAAiE,OAAO,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC;IACpK,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,KAAK,CAAC,cAAc;QAAE,OAAO;IAClC,KAAK,CAAC,wEAAwE,EAAE,MAAM,CAAC,CAAC;AAC1F,CAAC"}
@@ -5,6 +5,7 @@ import { state } from '../state.js';
5
5
  import { api } from '../api.js';
6
6
  import { escHtml } from '../utils.js';
7
7
  import { toast } from '../components/toast.js';
8
+ import type { NormalizeChange, NormalizePlan, NormalizeResponse } from '../api-types.js';
8
9
 
9
10
  export async function renderNormalizeView(): Promise<void> {
10
11
  const el = document.getElementById('content-normalize');
@@ -17,9 +18,9 @@ export async function renderNormalizeView(): Promise<void> {
17
18
  </div>
18
19
  <div id="normalize-content"><div class="loading-state"><div class="loading-spinner"></div></div></div>`;
19
20
  try {
20
- const data = await api('POST', `/projects/${state.currentProject.id}/pm/normalize`);
21
- const plan = (data as any).plan || (data as any).normalization || data;
22
- const items = plan.items || plan.changes || [];
21
+ const data = await api<NormalizeResponse>('POST', `/projects/${state.currentProject.id}/pm/normalize`);
22
+ const plan: NormalizePlan = data.plan || data.normalization || data;
23
+ const items: NormalizeChange[] = plan.items || plan.changes || [];
23
24
  const el2 = document.getElementById('normalize-content');
24
25
  if (!el2) return;
25
26
  if (Array.isArray(items) && items.length === 0) {
@@ -29,7 +30,7 @@ export async function renderNormalizeView(): Promise<void> {
29
30
  <div class="card">
30
31
  <div class="card-header"><div class="card-title">Suggested Changes (${items.length})</div></div>
31
32
  <div class="card-body">
32
- ${items.map((i: any) => `
33
+ ${items.map((i) => `
33
34
  <div style="display:flex;gap:8px;align-items:flex-start;padding:8px 0;border-bottom:1px solid var(--border)">
34
35
  <span style="color:var(--priority-3);flex-shrink:0">⚡</span>
35
36
  <div style="flex:1">
@@ -0,0 +1,230 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // PACKAGES VIEW — per-project pm package catalog
3
+ // ═══════════════════════════════════════════════════════════════
4
+ //
5
+ // Lists every pm package from the server-side catalog
6
+ // (src/services/package-catalog.ts, surfaced via
7
+ // GET /api/projects/:projectId/extensions) with install/enable/disable
8
+ // controls, the currently installed version, and an honest explanation for
9
+ // packages that need credentials or a backing service. Mutations go through
10
+ // the extensions routes, which validate the package name against the catalog
11
+ // before any pm command is spawned, and broadcast on the existing realtime bus
12
+ // so every collaborator on the project sees the change live.
13
+ //
14
+ // User-facing strings are wired into the i18n system (public/src/i18n.ts):
15
+ // the `packages.*` keys live in every shipped locale catalog, and the view
16
+ // re-renders translations on locale change via applyTranslations().
17
+ import { state } from '../state.js';
18
+ import { api } from '../api.js';
19
+ import { escHtml } from '../utils.js';
20
+ import { toast } from '../components/toast.js';
21
+ import { t, applyTranslations } from '../i18n.js';
22
+ let currentPackages = [];
23
+ /**
24
+ * Monotonic token for in-flight package fetches.
25
+ *
26
+ * A fetch started for one project can resolve *after* the user switches
27
+ * project or after a newer refresh, and would then paint stale cards into the
28
+ * new container — cards whose action buttons submit against the *current*
29
+ * project id. Each fetch captures the token it started with and discards its
30
+ * own response if a newer fetch has begun since.
31
+ */
32
+ let packagesFetchToken = 0;
33
+ export async function renderPackagesView() {
34
+ ensureActionsWired();
35
+ const el = document.getElementById('content-packages');
36
+ if (!el)
37
+ return;
38
+ if (!state.currentProject) {
39
+ el.innerHTML = `<div class="empty-state"><div class="empty-state-text">${escHtml(t('packages.noProject'))}</div></div>`;
40
+ return;
41
+ }
42
+ el.innerHTML = `
43
+ <div class="page-header">
44
+ <div>
45
+ <div class="page-title" data-i18n="packages.title">Packages</div>
46
+ <div class="page-subtitle">${escHtml(t('packages.subtitle', { project: state.currentProject.name }))}</div>
47
+ </div>
48
+ <div class="page-actions">
49
+ <button class="btn btn-secondary btn-sm" id="packages-refresh" onclick="window.__app.renderPackagesView()">${escHtml(t('packages.refresh'))}</button>
50
+ </div>
51
+ </div>
52
+ <div id="packages-content"><div class="loading-state"><div class="loading-spinner"></div></div></div>`;
53
+ await fetchAndRenderPackages();
54
+ }
55
+ async function fetchAndRenderPackages() {
56
+ const pid = state.currentProject?.id;
57
+ if (!pid)
58
+ return;
59
+ const target = document.getElementById('packages-content');
60
+ if (!target)
61
+ return;
62
+ const token = ++packagesFetchToken;
63
+ /** True when a newer fetch started, or the project changed, while awaiting. */
64
+ const superseded = () => token !== packagesFetchToken || state.currentProject?.id !== pid;
65
+ try {
66
+ const data = await api('GET', `/projects/${pid}/extensions`);
67
+ if (superseded())
68
+ return;
69
+ const rows = data.packages ?? [];
70
+ currentPackages = rows;
71
+ if (rows.length === 0) {
72
+ target.innerHTML = `<div class="empty-state"><div class="empty-state-text">${escHtml(t('packages.empty'))}</div></div>`;
73
+ return;
74
+ }
75
+ target.innerHTML = `
76
+ <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px">
77
+ ${rows.map((row) => renderPackageCard(row)).join('')}
78
+ </div>`;
79
+ applyTranslations(target);
80
+ }
81
+ catch (err) {
82
+ if (superseded())
83
+ return;
84
+ const msg = err instanceof Error ? err.message : String(err);
85
+ target.innerHTML = `<div class="empty-state"><div class="empty-state-text">${escHtml(t('packages.loadError', { error: msg }))}</div></div>`;
86
+ }
87
+ }
88
+ function renderPackageCard(row) {
89
+ const statusChip = row.installed
90
+ ? `<span style="font-size:11px;color:var(--text-muted);background:var(--bg-input);padding:2px 8px;border-radius:4px">${escHtml(t('packages.installed'))}${row.version ? ' · ' + escHtml(t('packages.version', { version: row.version })) : ''}</span>`
91
+ : `<span style="font-size:11px;color:var(--text-muted);background:var(--bg-input);padding:2px 8px;border-radius:4px">${escHtml(t('packages.notInstalled'))}</span>`;
92
+ // Badge authoring reference templates so a user can tell a learning
93
+ // scaffold from a product extension. Templates register real commands but
94
+ // exist to be read and copied; the badge makes that purpose explicit.
95
+ const templateBadge = row.category === 'template'
96
+ ? `<span style="font-size:11px;color:var(--text-secondary);background:var(--bg-accent);padding:2px 8px;border-radius:4px" data-i18n="packages.templateBadge">${escHtml(t('packages.templateBadge'))}</span>`
97
+ : '';
98
+ // Honest gating explanations — the UI must not promise a one-click install
99
+ // for a package that needs a Neo4j instance or an API token.
100
+ const serviceNote = row.requiresService
101
+ ? row.requiresService.optional
102
+ ? `<div class="pkg-req" style="font-size:12px;color:var(--text-secondary);margin-top:6px">⚠ ${escHtml(t('packages.requiresServiceOptional', { name: row.requiresService.name }))}</div>`
103
+ : `<div class="pkg-req" style="font-size:12px;color:var(--text-secondary);margin-top:6px">⚠ ${escHtml(t('packages.requiresService', { name: row.requiresService.name }))}</div>`
104
+ : '';
105
+ const credNotes = (row.requiresCredentials ?? []).map((c) => {
106
+ const label = c.optional ? t('packages.requiresCredentialsOptional') : t('packages.requiresCredentials');
107
+ const envs = c.envVars.map((e) => `<code style="font-family:var(--font-mono);background:var(--bg-input);padding:1px 4px;border-radius:3px">${escHtml(e)}</code>`).join(' ');
108
+ return `<div class="pkg-req" style="font-size:12px;color:var(--text-secondary);margin-top:4px">🔑 ${escHtml(label)} — ${escHtml(c.label)}<br><span style="color:var(--text-muted)">${escHtml(t('packages.envVars'))}: ${envs}</span></div>`;
109
+ }).join('');
110
+ const caps = row.capabilities.length
111
+ ? `<div style="font-size:11px;color:var(--text-muted);margin-top:6px">${escHtml(t('packages.capabilities'))}: ${row.capabilities.map((c) => escHtml(c)).join(', ')}</div>`
112
+ : '';
113
+ // Action buttons. The name is read from the row's catalog `name`, never from
114
+ // a raw DOM string — and the server validates it against the catalog again
115
+ // before any pm command is spawned.
116
+ let actions = '';
117
+ if (!row.installed) {
118
+ actions = `<button class="btn btn-primary btn-sm" style="width:100%" data-pkg-action="install" data-pkg-name="${escHtml(row.name)}">${escHtml(t('packages.install'))}</button>`;
119
+ }
120
+ else if (row.active && row.enabled) {
121
+ actions = `
122
+ <div style="display:flex;gap:6px">
123
+ <button class="btn btn-secondary btn-sm" style="flex:1" data-pkg-action="deactivate" data-pkg-name="${escHtml(row.name)}">${escHtml(t('packages.deactivate'))}</button>
124
+ <button class="btn btn-ghost btn-sm" style="flex:1" data-pkg-action="uninstall" data-pkg-name="${escHtml(row.name)}">${escHtml(t('packages.uninstall'))}</button>
125
+ </div>`;
126
+ }
127
+ else {
128
+ actions = `
129
+ <div style="display:flex;gap:6px">
130
+ <button class="btn btn-primary btn-sm" style="flex:1" data-pkg-action="activate" data-pkg-name="${escHtml(row.name)}">${escHtml(t('packages.activate'))}</button>
131
+ <button class="btn btn-ghost btn-sm" style="flex:1" data-pkg-action="uninstall" data-pkg-name="${escHtml(row.name)}">${escHtml(t('packages.uninstall'))}</button>
132
+ </div>`;
133
+ }
134
+ return `
135
+ <div class="card" style="cursor:default">
136
+ <div class="card-header" style="display:flex;align-items:center;justify-content:space-between;gap:8px">
137
+ <div style="display:flex;align-items:center;gap:6px">
138
+ <div class="card-title">${escHtml(row.title)}</div>
139
+ ${templateBadge}
140
+ </div>
141
+ ${statusChip}
142
+ </div>
143
+ <div class="card-body" style="padding-top:0">
144
+ <div style="font-size:12px;color:var(--text-muted);margin-bottom:8px;line-height:1.4">${escHtml(row.description)}</div>
145
+ ${serviceNote}${credNotes}${caps}
146
+ <div style="margin-top:12px">${actions}</div>
147
+ </div>
148
+ </div>`;
149
+ }
150
+ // Delegate clicks on package action buttons so each card stays simple and the
151
+ // handler resolves the action + name from data attributes, then calls the
152
+ // server-validated extensions route.
153
+ function wirePackageActions(root) {
154
+ root.addEventListener('click', (ev) => {
155
+ const btn = ev.target.closest('[data-pkg-action]');
156
+ if (!btn)
157
+ return;
158
+ const action = btn.dataset['pkgAction'];
159
+ const name = btn.dataset['pkgName'];
160
+ if (!action || !name)
161
+ return;
162
+ void handlePackageAction(name, action, btn);
163
+ });
164
+ }
165
+ async function handlePackageAction(name, action, btn) {
166
+ const pid = state.currentProject?.id;
167
+ if (!pid)
168
+ return;
169
+ const actionLabel = {
170
+ install: t('packages.install'),
171
+ activate: t('packages.activate'),
172
+ deactivate: t('packages.deactivate'),
173
+ uninstall: t('packages.uninstall'),
174
+ };
175
+ const original = btn.textContent ?? '';
176
+ btn.disabled = true;
177
+ btn.textContent = '…';
178
+ try {
179
+ if (action === 'uninstall') {
180
+ if (!window.confirm(t('packages.confirmUninstall', { name }))) {
181
+ btn.disabled = false;
182
+ btn.textContent = original;
183
+ return;
184
+ }
185
+ }
186
+ const method = action === 'uninstall' ? 'DELETE' : 'POST';
187
+ const suffix = action === 'uninstall' ? '' : `/${action}`;
188
+ await api(method, `/projects/${pid}/extensions/${encodeURIComponent(name)}${suffix}`);
189
+ toast(`${actionLabel[action] ?? action}: ${name}`, 'success');
190
+ await fetchAndRenderPackages();
191
+ }
192
+ catch (err) {
193
+ const msg = err instanceof Error ? err.message : String(err);
194
+ toast(t('packages.actionFailed', { action: actionLabel[action] ?? action, error: msg }), 'error');
195
+ btn.disabled = false;
196
+ btn.textContent = original;
197
+ }
198
+ }
199
+ // Wire the delegated action handler once the view container exists.
200
+ let actionsWired = false;
201
+ function ensureActionsWired() {
202
+ if (actionsWired)
203
+ return;
204
+ const root = document.getElementById('content-packages');
205
+ if (root) {
206
+ wirePackageActions(root);
207
+ actionsWired = true;
208
+ }
209
+ }
210
+ /**
211
+ * Called from the SSE handler in app.ts when an `extensions-changed` event
212
+ * arrives, so every collaborator on the project sees package installs /
213
+ * activates / deactivates / uninstalls live. Only re-renders when the Packages
214
+ * view is active, matching how the graph view gates its live refresh.
215
+ */
216
+ export function refreshPackagesOnSSE(evt) {
217
+ if (state.currentView !== 'packages')
218
+ return;
219
+ try {
220
+ const payload = JSON.parse(evt.data);
221
+ const myUserId = state.user?.id;
222
+ if (payload.userId && payload.userId !== myUserId) {
223
+ const op = payload.operation ?? 'updated';
224
+ toast(t('packages.changedByOther', { name: payload.name ?? '', operation: op }), 'info');
225
+ }
226
+ }
227
+ catch { /* ignore parse errors */ }
228
+ void fetchAndRenderPackages();
229
+ }
230
+ //# sourceMappingURL=packages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packages.js","sourceRoot":"","sources":["packages.ts"],"names":[],"mappings":"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"}