@unbrained/pm-web 2026.9.18 → 2026.9.26

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 (126) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -4
  3. package/dist/app.d.ts +10 -0
  4. package/dist/app.js +19 -9
  5. package/dist/app.js.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/routes/admin.js +6 -6
  8. package/dist/routes/admin.js.map +1 -1
  9. package/dist/routes/auth.js +2 -2
  10. package/dist/routes/auth.js.map +1 -1
  11. package/dist/routes/extensions.js +24 -37
  12. package/dist/routes/extensions.js.map +1 -1
  13. package/dist/routes/github.js +169 -107
  14. package/dist/routes/github.js.map +1 -1
  15. package/dist/routes/groups.js +9 -14
  16. package/dist/routes/groups.js.map +1 -1
  17. package/dist/routes/pm.js +532 -1015
  18. package/dist/routes/pm.js.map +1 -1
  19. package/dist/routes/projects.js +10 -6
  20. package/dist/routes/projects.js.map +1 -1
  21. package/dist/routes/route-helpers.d.ts +74 -0
  22. package/dist/routes/route-helpers.js +85 -0
  23. package/dist/routes/route-helpers.js.map +1 -0
  24. package/dist/routes/sharing.js +30 -18
  25. package/dist/routes/sharing.js.map +1 -1
  26. package/dist/server.js +1 -20
  27. package/dist/server.js.map +1 -1
  28. package/dist/services/mutation-event-watcher.js +3 -28
  29. package/dist/services/mutation-event-watcher.js.map +1 -1
  30. package/dist/services/pm-runner.js +3 -1
  31. package/dist/services/pm-runner.js.map +1 -1
  32. package/dist/services/project-watcher.js +14 -38
  33. package/dist/services/project-watcher.js.map +1 -1
  34. package/dist/services/watcher-utils.d.ts +42 -0
  35. package/dist/services/watcher-utils.js +60 -0
  36. package/dist/services/watcher-utils.js.map +1 -0
  37. package/manifest.json +2 -2
  38. package/package.json +17 -6
  39. package/public/src/app.js +22 -52
  40. package/public/src/app.js.map +1 -1
  41. package/public/src/app.ts +18 -75
  42. package/public/src/browser-window.js +29 -0
  43. package/public/src/browser-window.js.map +1 -0
  44. package/public/src/browser-window.ts +56 -0
  45. package/public/src/components/modals.js +1 -1
  46. package/public/src/components/modals.js.map +1 -1
  47. package/public/src/components/modals.ts +1 -1
  48. package/public/src/i18n.js +14 -6
  49. package/public/src/i18n.js.map +1 -1
  50. package/public/src/i18n.ts +16 -6
  51. package/public/src/sw.ts +1 -0
  52. package/public/src/utils.js +58 -0
  53. package/public/src/utils.js.map +1 -1
  54. package/public/src/utils.ts +53 -0
  55. package/public/src/views/admin.js +2 -5
  56. package/public/src/views/admin.js.map +1 -1
  57. package/public/src/views/admin.ts +2 -2
  58. package/public/src/views/auth.js +5 -1
  59. package/public/src/views/auth.js.map +1 -1
  60. package/public/src/views/auth.ts +6 -1
  61. package/public/src/views/calendar.js +1 -2
  62. package/public/src/views/calendar.js.map +1 -1
  63. package/public/src/views/calendar.ts +1 -2
  64. package/public/src/views/config.js +14 -21
  65. package/public/src/views/config.js.map +1 -1
  66. package/public/src/views/config.ts +14 -19
  67. package/public/src/views/context.js +11 -12
  68. package/public/src/views/context.js.map +1 -1
  69. package/public/src/views/context.ts +13 -12
  70. package/public/src/views/create.js +3 -11
  71. package/public/src/views/create.js.map +1 -1
  72. package/public/src/views/create.ts +3 -3
  73. package/public/src/views/export.js +2 -3
  74. package/public/src/views/export.js.map +1 -1
  75. package/public/src/views/export.ts +2 -3
  76. package/public/src/views/github.js +33 -47
  77. package/public/src/views/github.js.map +1 -1
  78. package/public/src/views/github.ts +35 -28
  79. package/public/src/views/graph-canvas.js +92 -99
  80. package/public/src/views/graph-canvas.js.map +1 -1
  81. package/public/src/views/graph-canvas.ts +94 -93
  82. package/public/src/views/graph.js +88 -73
  83. package/public/src/views/graph.js.map +1 -1
  84. package/public/src/views/graph.ts +97 -63
  85. package/public/src/views/groups.js +2 -5
  86. package/public/src/views/groups.js.map +1 -1
  87. package/public/src/views/groups.ts +2 -2
  88. package/public/src/views/guide.js +11 -10
  89. package/public/src/views/guide.js.map +1 -1
  90. package/public/src/views/guide.ts +12 -10
  91. package/public/src/views/items.js +100 -130
  92. package/public/src/views/items.js.map +1 -1
  93. package/public/src/views/items.ts +99 -117
  94. package/public/src/views/normalize.js +2 -9
  95. package/public/src/views/normalize.js.map +1 -1
  96. package/public/src/views/normalize.ts +2 -9
  97. package/public/src/views/packages.js +7 -4
  98. package/public/src/views/packages.js.map +1 -1
  99. package/public/src/views/packages.ts +8 -4
  100. package/public/src/views/plan-execution.js.map +1 -1
  101. package/public/src/views/plan-execution.ts +5 -15
  102. package/public/src/views/plan.js +2 -1
  103. package/public/src/views/plan.js.map +1 -1
  104. package/public/src/views/plan.ts +6 -17
  105. package/public/src/views/projects.js +5 -6
  106. package/public/src/views/projects.js.map +1 -1
  107. package/public/src/views/projects.ts +5 -6
  108. package/public/src/views/search.js +5 -1
  109. package/public/src/views/search.js.map +1 -1
  110. package/public/src/views/search.ts +7 -1
  111. package/public/src/views/settings.js +30 -55
  112. package/public/src/views/settings.js.map +1 -1
  113. package/public/src/views/settings.ts +23 -17
  114. package/public/src/views/shared.js +2 -5
  115. package/public/src/views/shared.js.map +1 -1
  116. package/public/src/views/shared.ts +2 -5
  117. package/public/src/views/sharing.js +2 -5
  118. package/public/src/views/sharing.js.map +1 -1
  119. package/public/src/views/sharing.ts +2 -2
  120. package/public/src/views/templates.js +9 -16
  121. package/public/src/views/templates.js.map +1 -1
  122. package/public/src/views/templates.ts +9 -14
  123. package/public/src/views/validate.js +2 -9
  124. package/public/src/views/validate.js.map +1 -1
  125. package/public/src/views/validate.ts +2 -9
  126. package/public/sw.js +1 -0
@@ -6,7 +6,8 @@ import { state } from '../state.js';
6
6
  import type { GraphNode, GraphRelationship, ProjectGraph } from '../types.js';
7
7
  import { escHtml } from '../utils.js';
8
8
  import { toast } from '../components/toast.js';
9
- import { GraphCanvas, type CanvasNode, type CanvasEdge, type LayoutMode } from './graph-canvas.js';
9
+ import { browserWindow } from '../browser-window.js';
10
+ import { GraphCanvas, type CanvasNode, type CanvasEdge, type LayoutMode, tagColorMapFromFreq } from './graph-canvas.js';
10
11
 
11
12
  type GraphResponse = {
12
13
  graph?: ProjectGraph;
@@ -34,6 +35,11 @@ let selectedNodeId = '';
34
35
  const canvasRef: { current: GraphCanvas | null } = { current: null };
35
36
  let physicsLabel = 'Pause Physics';
36
37
  let graphSyncInFlight = false;
38
+
39
+ /** Record whether a graph sync is running, outside the async sync function. */
40
+ function setGraphSyncInFlight(active: boolean): void {
41
+ graphSyncInFlight = active;
42
+ }
37
43
  let infoDrawerOpen = false;
38
44
  let relDrawerOpen = false;
39
45
  let filterOpen = false;
@@ -98,7 +104,7 @@ function showCtxMenu(nodeId: string, x: number, y: number): void {
98
104
  };
99
105
 
100
106
  if (isItem) {
101
- menu.appendChild(btn('⊡', 'Open Item', () => window.__app?.openItemDetail(nodeId)));
107
+ menu.appendChild(btn('⊡', 'Open Item', () => browserWindow().__app?.openItemDetail(nodeId)));
102
108
  const sep1 = document.createElement('div'); sep1.className = 'graph-ctx-sep'; menu.appendChild(sep1);
103
109
  }
104
110
  menu.appendChild(btn('⊙', 'Select & Focus', () => {
@@ -333,7 +339,7 @@ function blockerStats(rels: GraphRelationship[]): Map<string, { blockers: Set<st
333
339
  function visibleGraph(graph: ProjectGraph): { nodes: GraphNode[]; rels: GraphRelationship[]; connected: Set<string> } {
334
340
  const nodes = graph.nodes || [];
335
341
  let rels = graph.relationships || [];
336
- const connected = new Set(rels.flatMap((r) => [r.from, r.to]));
342
+ const connected = connectedNodeIds(rels);
337
343
 
338
344
  // Dep mode: restrict to dependency/block edges
339
345
  if (filter.depMode) {
@@ -379,24 +385,78 @@ function visibleGraph(graph: ProjectGraph): { nodes: GraphNode[]; rels: GraphRel
379
385
  return { nodes: visNodes, rels: visRels, connected };
380
386
  }
381
387
 
382
- // ── Canvas data conversion ────────────────────────────────────
388
+ // ── Shared graph helpers ─────────────────────────────────────
383
389
 
384
- /** Converts graph nodes into canvas display nodes, attaching each node's edge degree (computed from the relationships) so the renderer can size them. */
385
- function toCanvasNodes(nodes: GraphNode[], rels: GraphRelationship[]): CanvasNode[] {
386
- const deg = degreeMap(rels);
387
- return nodes.map((n) => ({
390
+ /** Returns the set of node ids that appear as either endpoint of any relationship in the given list. */
391
+ function connectedNodeIds(rels: GraphRelationship[]): Set<string> {
392
+ return new Set(rels.flatMap((r) => [r.from, r.to]));
393
+ }
394
+
395
+ /** Counts how many times each relationship type appears, returning a type-to-count record. */
396
+ function countRelTypes(rels: GraphRelationship[]): Record<string, number> {
397
+ return rels.reduce<Record<string, number>>((acc, r) => { acc[r.type] = (acc[r.type] || 0) + 1; return acc; }, {});
398
+ }
399
+
400
+ /** Counts how many nodes fall into each type category, returning a type-to-count record. */
401
+ function countNodeTypes(nodes: GraphNode[]): Record<string, number> {
402
+ return nodes.reduce<Record<string, number>>((acc, n) => {
403
+ const t = nodeType(n); acc[t] = (acc[t] || 0) + 1; return acc;
404
+ }, {});
405
+ }
406
+
407
+ /** Converts a single graph node into a canvas display node with the given edge degree, copying id, label, type, status, lane, degree, and tags. */
408
+ function nodeToCanvasNode(n: GraphNode, degree: number): CanvasNode {
409
+ return {
388
410
  id: n.id,
389
411
  label: nodeTitle(n),
390
412
  type: nodeType(n),
391
413
  status: nodeStatus(n),
392
414
  lane: nodeLane(n),
393
- degree: deg.get(n.id) || 0,
415
+ degree,
394
416
  tags: Array.isArray(n.properties?.tags)
395
417
  ? (n.properties.tags as unknown[]).map(String)
396
418
  : [],
397
- priority: n.properties?.priority !== undefined ? Number(n.properties.priority) : undefined,
398
- assignee: n.properties?.assignee ? String(n.properties.assignee) : undefined,
399
- }));
419
+ };
420
+ }
421
+
422
+ /** Destroys the current canvas instance and re-renders the graph view from scratch. */
423
+ function refreshGraph(): void {
424
+ canvasRef.current?.destroy();
425
+ canvasRef.current = null;
426
+ void renderGraphView();
427
+ }
428
+
429
+ /** Displays an error message on a form-error element, extracting the message from an unknown catch value. */
430
+ function displayFormError(errEl: HTMLElement | null, err: unknown): void {
431
+ if (errEl) { errEl.textContent = err instanceof Error ? err.message : String(err); errEl.style.display = ''; }
432
+ }
433
+
434
+ /** Reads the four physics-slider input elements from the DOM, returning null for any that are missing. */
435
+ function getPhysicsInputs(): {
436
+ rep: HTMLInputElement | null;
437
+ linkDist: HTMLInputElement | null;
438
+ linkStr: HTMLInputElement | null;
439
+ gravity: HTMLInputElement | null;
440
+ } {
441
+ return {
442
+ rep: document.getElementById('graph-physics-repulsion') as HTMLInputElement | null,
443
+ linkDist: document.getElementById('graph-physics-linkdist') as HTMLInputElement | null,
444
+ linkStr: document.getElementById('graph-physics-linkstr') as HTMLInputElement | null,
445
+ gravity: document.getElementById('graph-physics-gravity') as HTMLInputElement | null,
446
+ };
447
+ }
448
+
449
+ // ── Canvas data conversion ────────────────────────────────────
450
+
451
+ /** Converts graph nodes into canvas display nodes, attaching each node's edge degree (computed from the relationships) so the renderer can size them. */
452
+ function toCanvasNodes(nodes: GraphNode[], rels: GraphRelationship[]): CanvasNode[] {
453
+ const deg = degreeMap(rels);
454
+ return nodes.map((n) => {
455
+ const cn = nodeToCanvasNode(n, deg.get(n.id) || 0);
456
+ cn.priority = n.properties?.priority !== undefined ? Number(n.properties.priority) : undefined;
457
+ cn.assignee = n.properties?.assignee ? String(n.properties.assignee) : undefined;
458
+ return cn;
459
+ });
400
460
  }
401
461
 
402
462
  function toCanvasEdges(rels: GraphRelationship[]): CanvasEdge[] {
@@ -657,14 +717,12 @@ function renderInfoPanel(data: GraphResponse, fullItem?: Record<string, unknown>
657
717
  const nodes = graph.nodes || [];
658
718
  const rels = graph.relationships || [];
659
719
  const byId = new Map(nodes.map((n) => [n.id, n]));
660
- const connected = new Set(rels.flatMap((r) => [r.from, r.to]));
720
+ const connected = connectedNodeIds(rels);
661
721
  const itemNodes = nodes.filter(isItemNode);
662
- const relCounts = rels.reduce<Record<string, number>>((acc, r) => { acc[r.type] = (acc[r.type] || 0) + 1; return acc; }, {});
722
+ const relCounts = countRelTypes(rels);
663
723
  const selectedNode = selectedNodeId ? byId.get(selectedNodeId) : undefined;
664
724
 
665
- const typeCounts = nodes.reduce<Record<string, number>>((acc, n) => {
666
- const t = nodeType(n); acc[t] = (acc[t] || 0) + 1; return acc;
667
- }, {});
725
+ const typeCounts = countNodeTypes(nodes);
668
726
 
669
727
  return `
670
728
  <div class="graph-panel-title">Graph Coverage</div>
@@ -776,9 +834,9 @@ function renderGraphShell(data: GraphResponse): string {
776
834
  const rels = graph.relationships || [];
777
835
  const itemNodes = nodes.filter(isItemNode);
778
836
  const facetNodes = nodes.filter(isFacetNode);
779
- const connected = new Set(rels.flatMap((r) => [r.from, r.to]));
837
+ const connected = connectedNodeIds(rels);
780
838
  const isolated = itemNodes.filter((n) => !connected.has(n.id)).length;
781
- const relCounts = rels.reduce<Record<string, number>>((acc, r) => { acc[r.type] = (acc[r.type] || 0) + 1; return acc; }, {});
839
+ const relCounts = countRelTypes(rels);
782
840
  const relOptions = Object.keys(relCounts).sort();
783
841
  const errText = compactError(data.extensionError);
784
842
  const { rels: visRels } = visibleGraph(graph);
@@ -1053,7 +1111,7 @@ function initCanvas(): void {
1053
1111
  else selectedItemCache = null;
1054
1112
  },
1055
1113
  onOpenNode(id) {
1056
- window.__app?.openItemDetail(id);
1114
+ browserWindow().__app?.openItemDetail(id);
1057
1115
  },
1058
1116
  onContextMenu(id, x, y) { showCtxMenu(id, x, y); },
1059
1117
  });
@@ -1095,7 +1153,6 @@ const TYPE_COLORS_MAP: Record<string, string> = {
1095
1153
  task:'#2dd4bf', feature:'#60a5fa', epic:'#a78bfa', bug:'#f87171',
1096
1154
  milestone:'#fbbf24', story:'#34d399', chore:'#94a3b8', release:'#38bdf8',
1097
1155
  };
1098
- const TAG_PALETTE_JS = ['#2dd4bf','#60a5fa','#a78bfa','#f87171','#fbbf24','#34d399','#fb923c','#e879f9'];
1099
1156
 
1100
1157
  /** Computes a tag-to-color map by ranking tags by how often they appear across the nodes and assigning each of the most frequent ones a palette color. */
1101
1158
  function computeTagColorMap(nodes: GraphNode[]): Map<string, string> {
@@ -1104,8 +1161,7 @@ function computeTagColorMap(nodes: GraphNode[]): Map<string, string> {
1104
1161
  const tags = Array.isArray(n.properties?.tags) ? (n.properties.tags as unknown[]).map(String) : [];
1105
1162
  for (const t of tags) freq.set(t, (freq.get(t) ?? 0) + 1);
1106
1163
  }
1107
- const top = [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, TAG_PALETTE_JS.length).map(([t]) => t);
1108
- return new Map(top.map((t, i) => [t, TAG_PALETTE_JS[i]]));
1164
+ return tagColorMapFromFreq(freq);
1109
1165
  }
1110
1166
 
1111
1167
  /** Rebuilds the legend HUD HTML to match the current color mode (status, type, or tag) or dependency mode, including clickable tag chips when coloring by tag. */
@@ -1115,9 +1171,7 @@ function updateLegend(): void {
1115
1171
  const nodes = currentGraph?.graph?.nodes ?? [];
1116
1172
 
1117
1173
  if (filter.colorMode === 'type') {
1118
- const typeCounts = nodes.filter(isItemNode).reduce<Record<string, number>>((acc, n) => {
1119
- const t = nodeType(n); acc[t] = (acc[t] || 0) + 1; return acc;
1120
- }, {});
1174
+ const typeCounts = countNodeTypes(nodes.filter(isItemNode));
1121
1175
  const shown = Object.entries(typeCounts).sort((a, b) => b[1] - a[1]).slice(0, 6);
1122
1176
  legend.innerHTML = `
1123
1177
  <span><i class="legend-dot legend-facet"></i>Metadata</span>
@@ -1185,7 +1239,7 @@ function updateLegend(): void {
1185
1239
  /** Binds click handlers for the info-panel controls (open and clear the selected item, add and remove dependency) and for neighbor, tag, and relationship rows so they update the selection and canvas. */
1186
1240
  function bindInfoPanelEvents(): void {
1187
1241
  document.getElementById('graph-open-selected')?.addEventListener('click', () => {
1188
- if (selectedNodeId) window.__app?.openItemDetail(selectedNodeId);
1242
+ if (selectedNodeId) browserWindow().__app?.openItemDetail(selectedNodeId);
1189
1243
  });
1190
1244
  document.getElementById('graph-clear-selected')?.addEventListener('click', () => {
1191
1245
  selectedNodeId = '';
@@ -1245,15 +1299,15 @@ function bindHudEvents(): void {
1245
1299
  document.getElementById('graph-back-btn')?.addEventListener('click', () => {
1246
1300
  removeCtxMenu();
1247
1301
  // Remove graph keyboard handler
1248
- const kh = window.__graphKeyHandler;
1249
- if (kh) { document.removeEventListener('keydown', kh); delete window.__graphKeyHandler; }
1250
- window.__app?.showView('items');
1302
+ const kh = browserWindow().__graphKeyHandler;
1303
+ if (kh) { document.removeEventListener('keydown', kh); delete browserWindow().__graphKeyHandler; }
1304
+ browserWindow().__app?.showView('items');
1251
1305
  });
1252
1306
 
1253
1307
  const runGraphSync = async (): Promise<void> => {
1254
1308
  if (!state.currentProject || graphSyncInFlight) return;
1255
1309
  const syncBtn = document.getElementById('graph-sync-btn') as HTMLButtonElement | null;
1256
- graphSyncInFlight = true;
1310
+ setGraphSyncInFlight(true);
1257
1311
  if (syncBtn) {
1258
1312
  syncBtn.disabled = true;
1259
1313
  syncBtn.textContent = 'Syncing…';
@@ -1265,7 +1319,7 @@ function bindHudEvents(): void {
1265
1319
  } catch (err: unknown) {
1266
1320
  toast(err instanceof Error ? err.message : String(err), 'error');
1267
1321
  } finally {
1268
- graphSyncInFlight = false;
1322
+ setGraphSyncInFlight(false);
1269
1323
  if (syncBtn) {
1270
1324
  syncBtn.disabled = false;
1271
1325
  syncBtn.textContent = '⧉ Sync';
@@ -1439,10 +1493,7 @@ function bindHudEvents(): void {
1439
1493
  document.getElementById('graph-physics-panel-toggle')?.classList.toggle('active', physicsOpen);
1440
1494
  if (physicsOpen && canvasRef.current) {
1441
1495
  const params = canvasRef.current.getPhysicsParams();
1442
- const repEl = document.getElementById('graph-physics-repulsion') as HTMLInputElement | null;
1443
- const ldEl = document.getElementById('graph-physics-linkdist') as HTMLInputElement | null;
1444
- const lsEl = document.getElementById('graph-physics-linkstr') as HTMLInputElement | null;
1445
- const gEl = document.getElementById('graph-physics-gravity') as HTMLInputElement | null;
1496
+ const { rep: repEl, linkDist: ldEl, linkStr: lsEl, gravity: gEl } = getPhysicsInputs();
1446
1497
  if (repEl) { repEl.value = String(params.repulsion); }
1447
1498
  if (ldEl) { ldEl.value = String(params.linkDistance); }
1448
1499
  if (lsEl) { lsEl.value = String(Math.round(params.linkStrength * 100)); }
@@ -1471,10 +1522,7 @@ function bindHudEvents(): void {
1471
1522
 
1472
1523
  document.getElementById('graph-physics-reset')?.addEventListener('click', () => {
1473
1524
  canvasRef.current?.setPhysicsParams({ repulsion: 2000, linkDistance: 140, linkStrength: 0.065, centerForce: 0.010 });
1474
- const repEl = document.getElementById('graph-physics-repulsion') as HTMLInputElement | null;
1475
- const ldEl = document.getElementById('graph-physics-linkdist') as HTMLInputElement | null;
1476
- const lsEl = document.getElementById('graph-physics-linkstr') as HTMLInputElement | null;
1477
- const gEl = document.getElementById('graph-physics-gravity') as HTMLInputElement | null;
1525
+ const { rep: repEl, linkDist: ldEl, linkStr: lsEl, gravity: gEl } = getPhysicsInputs();
1478
1526
  if (repEl) repEl.value = '2000';
1479
1527
  if (ldEl) ldEl.value = '140';
1480
1528
  if (lsEl) lsEl.value = '7';
@@ -1572,7 +1620,7 @@ function bindHudEvents(): void {
1572
1620
  };
1573
1621
  document.addEventListener('keydown', graphKeyHandler);
1574
1622
  // Store for cleanup on graph exit
1575
- window.__graphKeyHandler = graphKeyHandler;
1623
+ browserWindow().__graphKeyHandler = graphKeyHandler;
1576
1624
  }
1577
1625
 
1578
1626
  // ── URL routing (pushState) ─────────────────────────────────
@@ -1674,12 +1722,9 @@ function showAddDependencyModal(): void {
1674
1722
  try {
1675
1723
  await api('POST', `/projects/${state.currentProject!.id}/pm/deps/${fromId}`, { targetId: toId, rel: relType });
1676
1724
  document.getElementById('graph-add-dep-modal')?.remove();
1677
- // Refresh graph
1678
- canvasRef.current?.destroy();
1679
- canvasRef.current = null;
1680
- void renderGraphView();
1725
+ refreshGraph();
1681
1726
  } catch (err: unknown) {
1682
- if (errEl) { errEl.textContent = err instanceof Error ? err.message : String(err); errEl.style.display = ''; }
1727
+ displayFormError(errEl, err);
1683
1728
  }
1684
1729
  });
1685
1730
  }
@@ -1699,7 +1744,7 @@ function showRemoveDependencyModal(): void {
1699
1744
  }).join('');
1700
1745
 
1701
1746
  if (!depRels.length) {
1702
- window.__app?.toast('No dependencies to remove', 'info');
1747
+ browserWindow().__app?.toast('No dependencies to remove', 'info');
1703
1748
  return;
1704
1749
  }
1705
1750
 
@@ -1739,12 +1784,9 @@ function showRemoveDependencyModal(): void {
1739
1784
  try {
1740
1785
  await api('DELETE', `/projects/${state.currentProject!.id}/pm/deps/${rel.from}`, { targetId: rel.to, rel: rel.type });
1741
1786
  document.getElementById('graph-remove-dep-modal')?.remove();
1742
- // Refresh graph
1743
- canvasRef.current?.destroy();
1744
- canvasRef.current = null;
1745
- void renderGraphView();
1787
+ refreshGraph();
1746
1788
  } catch (err: unknown) {
1747
- if (errEl) { errEl.textContent = err instanceof Error ? err.message : String(err); errEl.style.display = ''; }
1789
+ displayFormError(errEl, err);
1748
1790
  }
1749
1791
  });
1750
1792
  }
@@ -1893,25 +1935,17 @@ export async function renderLocalGraph(
1893
1935
  if (id !== nodeId) {
1894
1936
  const n = nodes.find((nd) => nd.id === id);
1895
1937
  if (n && isItemNode(n)) {
1896
- window.__app?.openItemDetail(id);
1938
+ browserWindow().__app?.openItemDetail(id);
1897
1939
  }
1898
1940
  }
1899
1941
  },
1900
1942
  onOpenNode(id) {
1901
- window.__app?.openItemDetail(id);
1943
+ browserWindow().__app?.openItemDetail(id);
1902
1944
  },
1903
1945
  onContextMenu() { /* no context menu in local graph */ },
1904
1946
  });
1905
1947
 
1906
- const canvasNodes: CanvasNode[] = subNodes.map((n) => ({
1907
- id: n.id,
1908
- label: nodeTitle(n),
1909
- type: nodeType(n),
1910
- status: nodeStatus(n),
1911
- lane: nodeLane(n),
1912
- degree: deg.get(n.id) || 0,
1913
- tags: Array.isArray(n.properties?.tags) ? (n.properties.tags as unknown[]).map(String) : [],
1914
- }));
1948
+ const canvasNodes: CanvasNode[] = subNodes.map((n) => nodeToCanvasNode(n, deg.get(n.id) || 0));
1915
1949
  const canvasEdges: CanvasEdge[] = subRels.map((r) => ({ from: r.from, to: r.to, type: r.type }));
1916
1950
 
1917
1951
  canvas.setData(canvasNodes, canvasEdges);
@@ -1,5 +1,5 @@
1
1
  import { api } from '../api.js';
2
- import { escHtml } from '../utils.js';
2
+ import { escHtml, showError } from '../utils.js';
3
3
  import { showModal, hideModal, createModal, confirmDialog } from '../components/modals.js';
4
4
  import { toast } from '../components/toast.js';
5
5
  /**
@@ -104,10 +104,7 @@ export async function submitCreateGroup() {
104
104
  await loadGroups();
105
105
  }
106
106
  catch (err) {
107
- if (errEl) {
108
- errEl.textContent = err instanceof Error ? err.message : String(err);
109
- errEl.style.display = 'block';
110
- }
107
+ showError(errEl, err);
111
108
  }
112
109
  }
113
110
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"groups.js","sourceRoot":"","sources":["groups.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"groups.js","sourceRoot":"","sources":["groups.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  // ═══════════════════════════════════════════════════════════════
4
4
  import { state } from '../state.js';
5
5
  import { api } from '../api.js';
6
- import { escHtml } from '../utils.js';
6
+ import { escHtml, showError } from '../utils.js';
7
7
  import { showModal, hideModal, createModal, confirmDialog } from '../components/modals.js';
8
8
  import { toast } from '../components/toast.js';
9
9
  import type { GroupDetailResponse, GroupMemberRow, GroupsResponse, GroupListRow } from '../api-types.js';
@@ -103,7 +103,7 @@ export async function submitCreateGroup(): Promise<void> {
103
103
  hideModal('create-group-modal');
104
104
  await loadGroups();
105
105
  } catch(err: unknown) {
106
- if (errEl) { errEl.textContent = err instanceof Error ? err.message : String(err); errEl.style.display = 'block'; }
106
+ showError(errEl, err);
107
107
  }
108
108
  }
109
109
 
@@ -5,6 +5,15 @@ import { state } from '../state.js';
5
5
  import { getGuide, getGuideTopic } from '../api.js';
6
6
  import { escHtml } from '../utils.js';
7
7
  import { toast } from '../components/toast.js';
8
+ /** Replaces the guide content with the shared error state and toasts the
9
+ * failure, falling back to the given message when the error has none. */
10
+ function showGuideError(err, fallback) {
11
+ const contentEl = document.getElementById('guide-content');
12
+ if (contentEl) {
13
+ contentEl.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
14
+ }
15
+ toast(err instanceof Error ? err.message : fallback, 'error');
16
+ }
8
17
  const TOPIC_ICONS = {
9
18
  quickstart: '🚀',
10
19
  commands: '⌨️',
@@ -120,11 +129,7 @@ export async function renderGuideView(topicId) {
120
129
  contentEl.innerHTML = renderTopicDetail(topic);
121
130
  }
122
131
  catch (err) {
123
- const contentEl = document.getElementById('guide-content');
124
- if (contentEl) {
125
- contentEl.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
126
- }
127
- toast(err instanceof Error ? err.message : 'Failed to load topic', 'error');
132
+ showGuideError(err, 'Failed to load topic');
128
133
  }
129
134
  }
130
135
  else {
@@ -143,11 +148,7 @@ export async function renderGuideView(topicId) {
143
148
  contentEl.innerHTML = renderTopicCards(topics);
144
149
  }
145
150
  catch (err) {
146
- const contentEl = document.getElementById('guide-content');
147
- if (contentEl) {
148
- contentEl.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
149
- }
150
- toast(err instanceof Error ? err.message : 'Failed to load guide', 'error');
151
+ showGuideError(err, 'Failed to load guide');
151
152
  }
152
153
  }
153
154
  }
@@ -1 +1 @@
1
- {"version":3,"file":"guide.js","sourceRoot":"","sources":["guide.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"guide.js","sourceRoot":"","sources":["guide.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,16 @@ import { getGuide, getGuideTopic } from '../api.js';
6
6
  import { escHtml } from '../utils.js';
7
7
  import { toast } from '../components/toast.js';
8
8
 
9
+ /** Replaces the guide content with the shared error state and toasts the
10
+ * failure, falling back to the given message when the error has none. */
11
+ function showGuideError(err: unknown, fallback: string): void {
12
+ const contentEl = document.getElementById('guide-content');
13
+ if (contentEl) {
14
+ contentEl.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
15
+ }
16
+ toast(err instanceof Error ? err.message : fallback, 'error');
17
+ }
18
+
9
19
  const TOPIC_ICONS: Record<string, string> = {
10
20
  quickstart: '🚀',
11
21
  commands: '⌨️',
@@ -134,11 +144,7 @@ export async function renderGuideView(topicId?: string): Promise<void> {
134
144
  const contentEl = document.getElementById('guide-content');
135
145
  if (contentEl) contentEl.innerHTML = renderTopicDetail(topic as Parameters<typeof renderTopicDetail>[0]);
136
146
  } catch (err: unknown) {
137
- const contentEl = document.getElementById('guide-content');
138
- if (contentEl) {
139
- contentEl.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
140
- }
141
- toast(err instanceof Error ? err.message : 'Failed to load topic', 'error');
147
+ showGuideError(err, 'Failed to load topic');
142
148
  }
143
149
  } else {
144
150
  // Show topic list
@@ -155,11 +161,7 @@ export async function renderGuideView(topicId?: string): Promise<void> {
155
161
  const contentEl = document.getElementById('guide-content');
156
162
  if (contentEl) contentEl.innerHTML = renderTopicCards(topics as Parameters<typeof renderTopicCards>[0]);
157
163
  } catch (err: unknown) {
158
- const contentEl = document.getElementById('guide-content');
159
- if (contentEl) {
160
- contentEl.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
161
- }
162
- toast(err instanceof Error ? err.message : 'Failed to load guide', 'error');
164
+ showGuideError(err, 'Failed to load guide');
163
165
  }
164
166
  }
165
167
  }