@unbrained/pm-web 2026.7.28 → 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 (96) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +1 -1
  3. package/dist/oidc.js.map +1 -1
  4. package/dist/routes/extensions.d.ts +1 -1
  5. package/dist/routes/oidc.d.ts +1 -1
  6. package/dist/routes/pm.js +125 -0
  7. package/dist/routes/pm.js.map +1 -1
  8. package/dist/services/mutation-event-watcher.d.ts +1 -1
  9. package/dist/services/package-catalog.d.ts +28 -4
  10. package/dist/services/package-catalog.js +67 -5
  11. package/dist/services/package-catalog.js.map +1 -1
  12. package/dist/services/pm-runner.d.ts +1 -1
  13. package/dist/services/pm-runner.js +8 -1
  14. package/dist/services/pm-runner.js.map +1 -1
  15. package/dist/services/project-watcher.d.ts +1 -1
  16. package/dist/services/realtime-bus.d.ts +1 -1
  17. package/manifest.json +1 -1
  18. package/package.json +21 -6
  19. package/public/src/api-types.js +17 -0
  20. package/public/src/api-types.js.map +1 -0
  21. package/public/src/api-types.ts +618 -0
  22. package/public/src/api.js +6 -3
  23. package/public/src/api.js.map +1 -1
  24. package/public/src/api.ts +30 -10
  25. package/public/src/app.js +4 -4
  26. package/public/src/app.js.map +1 -1
  27. package/public/src/app.ts +47 -12
  28. package/public/src/i18n/de.json +2 -1
  29. package/public/src/i18n/en.json +2 -1
  30. package/public/src/i18n/es.json +2 -1
  31. package/public/src/i18n/zh.json +2 -1
  32. package/public/src/i18n.ts +1 -1
  33. package/public/src/sw.ts +1 -0
  34. package/public/src/types.ts +11 -2
  35. package/public/src/views/activity.js +1 -1
  36. package/public/src/views/activity.js.map +1 -1
  37. package/public/src/views/activity.ts +5 -4
  38. package/public/src/views/calendar.js +1 -1
  39. package/public/src/views/calendar.js.map +1 -1
  40. package/public/src/views/calendar.ts +13 -11
  41. package/public/src/views/comments-audit.js.map +1 -1
  42. package/public/src/views/comments-audit.ts +7 -6
  43. package/public/src/views/config.js +1 -1
  44. package/public/src/views/config.js.map +1 -1
  45. package/public/src/views/config.ts +2 -2
  46. package/public/src/views/context.js +3 -3
  47. package/public/src/views/context.js.map +1 -1
  48. package/public/src/views/context.ts +10 -9
  49. package/public/src/views/create.js.map +1 -1
  50. package/public/src/views/create.ts +3 -2
  51. package/public/src/views/dedupe.js +5 -5
  52. package/public/src/views/dedupe.js.map +1 -1
  53. package/public/src/views/dedupe.ts +9 -8
  54. package/public/src/views/export.js.map +1 -1
  55. package/public/src/views/export.ts +2 -2
  56. package/public/src/views/github.js +5 -4
  57. package/public/src/views/github.js.map +1 -1
  58. package/public/src/views/github.ts +20 -18
  59. package/public/src/views/graph.js +7 -9
  60. package/public/src/views/graph.js.map +1 -1
  61. package/public/src/views/graph.ts +11 -13
  62. package/public/src/views/groups.js +1 -1
  63. package/public/src/views/groups.js.map +1 -1
  64. package/public/src/views/groups.ts +9 -8
  65. package/public/src/views/health.js +16 -2
  66. package/public/src/views/health.js.map +1 -1
  67. package/public/src/views/health.ts +26 -8
  68. package/public/src/views/items.js +18 -12
  69. package/public/src/views/items.js.map +1 -1
  70. package/public/src/views/items.ts +65 -35
  71. package/public/src/views/normalize.js.map +1 -1
  72. package/public/src/views/normalize.ts +5 -4
  73. package/public/src/views/packages.js +10 -1
  74. package/public/src/views/packages.js.map +1 -1
  75. package/public/src/views/packages.ts +19 -2
  76. package/public/src/views/plan.js +0 -2
  77. package/public/src/views/plan.js.map +1 -1
  78. package/public/src/views/plan.ts +5 -6
  79. package/public/src/views/projects.js.map +1 -1
  80. package/public/src/views/projects.ts +8 -7
  81. package/public/src/views/search.js.map +1 -1
  82. package/public/src/views/search.ts +3 -2
  83. package/public/src/views/settings.js.map +1 -1
  84. package/public/src/views/settings.ts +8 -6
  85. package/public/src/views/shared.js.map +1 -1
  86. package/public/src/views/shared.ts +4 -3
  87. package/public/src/views/sharing.js.map +1 -1
  88. package/public/src/views/sharing.ts +5 -4
  89. package/public/src/views/stats.js +1 -1
  90. package/public/src/views/stats.js.map +1 -1
  91. package/public/src/views/stats.ts +10 -9
  92. package/public/src/views/templates.js.map +1 -1
  93. package/public/src/views/templates.ts +6 -5
  94. package/public/src/views/validate.js.map +1 -1
  95. package/public/src/views/validate.ts +6 -5
  96. package/public/sw.js +1 -0
@@ -97,7 +97,7 @@ function showCtxMenu(nodeId: string, x: number, y: number): void {
97
97
  };
98
98
 
99
99
  if (isItem) {
100
- menu.appendChild(btn('⊡', 'Open Item', () => (window as unknown as { __app: { openItemDetail(id: string): void } }).__app.openItemDetail(nodeId)));
100
+ menu.appendChild(btn('⊡', 'Open Item', () => window.__app?.openItemDetail(nodeId)));
101
101
  const sep1 = document.createElement('div'); sep1.className = 'graph-ctx-sep'; menu.appendChild(sep1);
102
102
  }
103
103
  menu.appendChild(btn('⊙', 'Select & Focus', () => {
@@ -1025,7 +1025,7 @@ function initCanvas(): void {
1025
1025
  else selectedItemCache = null;
1026
1026
  },
1027
1027
  onOpenNode(id) {
1028
- (window as unknown as { __app: { openItemDetail(id: string): void } }).__app.openItemDetail(id);
1028
+ window.__app?.openItemDetail(id);
1029
1029
  },
1030
1030
  onContextMenu(id, x, y) { showCtxMenu(id, x, y); },
1031
1031
  });
@@ -1152,7 +1152,7 @@ function updateLegend(): void {
1152
1152
 
1153
1153
  function bindInfoPanelEvents(): void {
1154
1154
  document.getElementById('graph-open-selected')?.addEventListener('click', () => {
1155
- if (selectedNodeId) (window as unknown as { __app: { openItemDetail(id: string): void } }).__app.openItemDetail(selectedNodeId);
1155
+ if (selectedNodeId) window.__app?.openItemDetail(selectedNodeId);
1156
1156
  });
1157
1157
  document.getElementById('graph-clear-selected')?.addEventListener('click', () => {
1158
1158
  selectedNodeId = '';
@@ -1211,9 +1211,9 @@ function bindHudEvents(): void {
1211
1211
  document.getElementById('graph-back-btn')?.addEventListener('click', () => {
1212
1212
  removeCtxMenu();
1213
1213
  // Remove graph keyboard handler
1214
- const kh = (window as unknown as { __graphKeyHandler?: (e: KeyboardEvent) => void }).__graphKeyHandler;
1215
- if (kh) { document.removeEventListener('keydown', kh); delete (window as unknown as { __graphKeyHandler?: unknown }).__graphKeyHandler; }
1216
- (window as unknown as { __app: { showView(v: string): void } }).__app.showView('items');
1214
+ const kh = window.__graphKeyHandler;
1215
+ if (kh) { document.removeEventListener('keydown', kh); delete window.__graphKeyHandler; }
1216
+ window.__app?.showView('items');
1217
1217
  });
1218
1218
 
1219
1219
  const runGraphSync = async (): Promise<void> => {
@@ -1538,7 +1538,7 @@ function bindHudEvents(): void {
1538
1538
  };
1539
1539
  document.addEventListener('keydown', graphKeyHandler);
1540
1540
  // Store for cleanup on graph exit
1541
- (window as unknown as { __graphKeyHandler?: (e: KeyboardEvent) => void }).__graphKeyHandler = graphKeyHandler;
1541
+ window.__graphKeyHandler = graphKeyHandler;
1542
1542
  }
1543
1543
 
1544
1544
  // ── URL routing (pushState) ─────────────────────────────────
@@ -1661,7 +1661,7 @@ function showRemoveDependencyModal(): void {
1661
1661
  }).join('');
1662
1662
 
1663
1663
  if (!depRels.length) {
1664
- (window as unknown as { __app: { toast(msg: string, type: string): void } }).__app.toast('No dependencies to remove', 'info');
1664
+ window.__app?.toast('No dependencies to remove', 'info');
1665
1665
  return;
1666
1666
  }
1667
1667
 
@@ -1748,7 +1748,7 @@ export async function renderGraphView(): Promise<void> {
1748
1748
 
1749
1749
  // Restore selected node after canvas init
1750
1750
  if (selectedNodeId) {
1751
- (canvasRef.current as any)?.setSelected(selectedNodeId);
1751
+ (canvasRef.current as GraphCanvas | null)?.setSelected(selectedNodeId);
1752
1752
  updateInfoPanel();
1753
1753
  syncCanvas();
1754
1754
  // Auto-open info drawer when node is restored from URL
@@ -1852,14 +1852,12 @@ export async function renderLocalGraph(
1852
1852
  if (id !== nodeId) {
1853
1853
  const n = nodes.find((nd) => nd.id === id);
1854
1854
  if (n && isItemNode(n)) {
1855
- const appw = window as unknown as { __app?: { openItemDetail(id: string): void } };
1856
- appw.__app?.openItemDetail(id);
1855
+ window.__app?.openItemDetail(id);
1857
1856
  }
1858
1857
  }
1859
1858
  },
1860
1859
  onOpenNode(id) {
1861
- const appw = window as unknown as { __app?: { openItemDetail(id: string): void } };
1862
- appw.__app?.openItemDetail(id);
1860
+ window.__app?.openItemDetail(id);
1863
1861
  },
1864
1862
  onContextMenu() { /* no context menu in local graph */ },
1865
1863
  });
@@ -43,7 +43,7 @@ async function loadGroups() {
43
43
  <div class="group-name">${escHtml(g.name)}</div>
44
44
  ${g.description ? `<div class="group-desc">${escHtml(g.description)}</div>` : ''}
45
45
  </div>
46
- <span class="sidebar-badge">${g.memberCount || g.members?.length || 0} members</span>
46
+ <span class="sidebar-badge">${g.member_count || 0} members</span>
47
47
  <button class="btn btn-danger btn-sm" onclick="event.stopPropagation();window.__app.deleteGroup('${escHtml(g.id)}','${escHtml(g.name)}')">Delete</button>
48
48
  </div>`).join('')}
49
49
  </div>`;
@@ -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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"}
@@ -6,6 +6,7 @@ import { api } from '../api.js';
6
6
  import { escHtml } from '../utils.js';
7
7
  import { showModal, hideModal, createModal, confirmDialog } from '../components/modals.js';
8
8
  import { toast } from '../components/toast.js';
9
+ import type { GroupDetailResponse, GroupMemberRow, GroupsResponse, GroupListRow } from '../api-types.js';
9
10
 
10
11
  export async function renderGroupsView(): Promise<void> {
11
12
  const el = document.getElementById('content-groups');
@@ -26,8 +27,8 @@ async function loadGroups(): Promise<void> {
26
27
  const el = document.getElementById('groups-list');
27
28
  if (!el) return;
28
29
  try {
29
- const data = await api('GET','/groups');
30
- const groups = (data as any).groups || [];
30
+ const data = await api<GroupsResponse>('GET','/groups');
31
+ const groups: GroupListRow[] = data.groups || [];
31
32
  if (groups.length === 0) {
32
33
  el.innerHTML = `
33
34
  <div class="empty-state">
@@ -38,7 +39,7 @@ async function loadGroups(): Promise<void> {
38
39
  return;
39
40
  }
40
41
  el.innerHTML = `<div style="display:flex;flex-direction:column;gap:8px">
41
- ${groups.map((g: any)=>`
42
+ ${groups.map((g)=>`
42
43
  <div class="group-row" onclick="window.__app.openGroupDetail('${escHtml(g.id)}','${escHtml(g.name)}')">
43
44
  <div style="width:36px;height:36px;border-radius:50%;background:var(--accent-dim);display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:600;color:var(--accent);flex-shrink:0">
44
45
  ${escHtml((g.name||'?').slice(0,2).toUpperCase())}
@@ -47,7 +48,7 @@ async function loadGroups(): Promise<void> {
47
48
  <div class="group-name">${escHtml(g.name)}</div>
48
49
  ${g.description ? `<div class="group-desc">${escHtml(g.description)}</div>` : ''}
49
50
  </div>
50
- <span class="sidebar-badge">${g.memberCount||g.members?.length||0} members</span>
51
+ <span class="sidebar-badge">${g.member_count||0} members</span>
51
52
  <button class="btn btn-danger btn-sm" onclick="event.stopPropagation();window.__app.deleteGroup('${escHtml(g.id)}','${escHtml(g.name)}')">Delete</button>
52
53
  </div>`).join('')}
53
54
  </div>`;
@@ -105,14 +106,14 @@ export async function openGroupDetail(groupId: string, _groupName: string): Prom
105
106
  showModal('group-detail-modal');
106
107
 
107
108
  try {
108
- const data = await api('GET',`/groups/${groupId}`);
109
- const group = (data as any).group || {id:groupId,name:_groupName,members:[]};
110
- const members = group.members || [];
109
+ const data = await api<GroupDetailResponse>('GET',`/groups/${groupId}`);
110
+ const group = data.group || {id:groupId,name:_groupName,members:[]};
111
+ const members: GroupMemberRow[] = group.members || [];
111
112
  const groupName = group.name || _groupName || 'Group';
112
113
 
113
114
  const membersHtml = members.length === 0
114
115
  ? `<div style="color:var(--text-muted);font-size:13px">No members yet</div>`
115
- : members.map((m: any)=>`
116
+ : members.map((m)=>`
116
117
  <div class="member-row">
117
118
  <div class="member-avatar">${escHtml((m.display_name||m.displayName||m.email||'?').slice(0,2).toUpperCase())}</div>
118
119
  <div style="flex:1">
@@ -31,11 +31,25 @@ function renderIssueRow(issue, projectId) {
31
31
  export async function repairItemHistory(projectId, itemId, dryRun) {
32
32
  try {
33
33
  const data = await api('POST', `/projects/${projectId}/pm/items/${encodeURIComponent(itemId)}/history-repair`, { dryRun });
34
+ // `pm history-repair --json` carries no prose field, so summarise the counts
35
+ // it does return rather than dumping the whole payload into the toast.
36
+ const h = data.history;
37
+ const parts = [
38
+ `${h?.entries_scanned ?? 0} scanned`,
39
+ ...(h?.entries_rehashed ? [`${h.entries_rehashed} rehashed`] : []),
40
+ ...(h?.entries_patch_repaired ? [`${h.entries_patch_repaired} patched`] : []),
41
+ ...(h?.reconciled_with_item ? ['reconciled with item'] : []),
42
+ ];
34
43
  if (dryRun) {
35
- toast(`Dry run for ${itemId}: ${data.message || JSON.stringify(data)}`, 'info');
44
+ toast(data.changed
45
+ ? `Dry run for ${itemId}: drift found — ${parts.join(', ')}`
46
+ : `Dry run for ${itemId}: no drift (${parts.join(', ')})`, 'info');
47
+ }
48
+ else if (h?.verify_ok === false) {
49
+ toast(`History repaired for ${itemId} but verification failed: ${(h.verify_errors ?? []).join('; ') || 'unknown error'}`, 'error');
36
50
  }
37
51
  else {
38
- toast(`History repaired for ${itemId}`, 'success');
52
+ toast(`History repaired for ${itemId} — ${parts.join(', ')}`, 'success');
39
53
  }
40
54
  }
41
55
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"health.js","sourceRoot":"","sources":["health.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["health.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,8 @@ 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 { HealthResponse, HistoryRepairResponse } from '../api-types.js';
9
+ import type { HealthIssue } from '../types.js';
8
10
 
9
11
  // Detect item IDs mentioned in a history-drift issue message.
10
12
  function extractItemIdFromIssue(msg: string): string | null {
@@ -13,12 +15,12 @@ function extractItemIdFromIssue(msg: string): string | null {
13
15
  return match ? match[1] : null;
14
16
  }
15
17
 
16
- function isHistoryDriftIssue(issue: any): boolean {
18
+ function isHistoryDriftIssue(issue: HealthIssue): boolean {
17
19
  const msg: string = (issue.message || issue.description || issue.type || '').toLowerCase();
18
20
  return msg.includes('history') && (msg.includes('drift') || msg.includes('repair') || msg.includes('mismatch'));
19
21
  }
20
22
 
21
- function renderIssueRow(issue: any, projectId: string): string {
23
+ function renderIssueRow(issue: HealthIssue, projectId: string): string {
22
24
  const msg = escHtml(issue.message || issue.description || String(issue));
23
25
  if (isHistoryDriftIssue(issue)) {
24
26
  const itemId = extractItemIdFromIssue(issue.message || issue.description || '');
@@ -34,11 +36,27 @@ function renderIssueRow(issue: any, projectId: string): string {
34
36
 
35
37
  export async function repairItemHistory(projectId: string, itemId: string, dryRun: boolean): Promise<void> {
36
38
  try {
37
- const data = await api('POST', `/projects/${projectId}/pm/items/${encodeURIComponent(itemId)}/history-repair`, { dryRun });
39
+ const data = await api<HistoryRepairResponse>('POST', `/projects/${projectId}/pm/items/${encodeURIComponent(itemId)}/history-repair`, { dryRun });
40
+ // `pm history-repair --json` carries no prose field, so summarise the counts
41
+ // it does return rather than dumping the whole payload into the toast.
42
+ const h = data.history;
43
+ const parts = [
44
+ `${h?.entries_scanned ?? 0} scanned`,
45
+ ...(h?.entries_rehashed ? [`${h.entries_rehashed} rehashed`] : []),
46
+ ...(h?.entries_patch_repaired ? [`${h.entries_patch_repaired} patched`] : []),
47
+ ...(h?.reconciled_with_item ? ['reconciled with item'] : []),
48
+ ];
38
49
  if (dryRun) {
39
- toast(`Dry run for ${itemId}: ${(data as any).message || JSON.stringify(data)}`, 'info');
50
+ toast(
51
+ data.changed
52
+ ? `Dry run for ${itemId}: drift found — ${parts.join(', ')}`
53
+ : `Dry run for ${itemId}: no drift (${parts.join(', ')})`,
54
+ 'info',
55
+ );
56
+ } else if (h?.verify_ok === false) {
57
+ toast(`History repaired for ${itemId} but verification failed: ${(h.verify_errors ?? []).join('; ') || 'unknown error'}`, 'error');
40
58
  } else {
41
- toast(`History repaired for ${itemId}`, 'success');
59
+ toast(`History repaired for ${itemId} — ${parts.join(', ')}`, 'success');
42
60
  }
43
61
  } catch(err: unknown) {
44
62
  toast(`Repair failed: ${err instanceof Error ? err.message : String(err)}`, 'error');
@@ -58,8 +76,8 @@ export async function renderHealthView(): Promise<void> {
58
76
  <div id="health-content"><div class="loading-state"><div class="loading-spinner"></div></div></div>`;
59
77
 
60
78
  try {
61
- const data = await api('GET',`/projects/${projectId}/pm/health`);
62
- const health = (data as any).health || data;
79
+ const data = await api<HealthResponse>('GET',`/projects/${projectId}/pm/health`);
80
+ const health = data.health || data;
63
81
  const score = health.score !== undefined ? health.score : null;
64
82
  const issues = health.issues || [];
65
83
 
@@ -86,7 +104,7 @@ export async function renderHealthView(): Promise<void> {
86
104
  <div class="card-body">
87
105
  ${issues.length === 0
88
106
  ? '<div style="color:var(--status-closed);font-size:13px">✓ No issues found — project looks healthy!</div>'
89
- : issues.map((i: any) => renderIssueRow(i, projectId)).join('')
107
+ : issues.map((i) => renderIssueRow(i, projectId)).join('')
90
108
  }
91
109
  </div>
92
110
  </div>
@@ -11,6 +11,11 @@ import { showView } from './router.js';
11
11
  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
+ // Stash the bulk-update / bulk-close payloads on the apply buttons without
15
+ // mutating the DOM nodes. A module-scoped WeakMap per modal keeps the payload
16
+ // alive across the Preview → Apply call pair and is GC-friendly.
17
+ const bulkUpdatePayloads = new WeakMap();
18
+ const bulkClosePayloads = new WeakMap();
14
19
  // Mirror the active item filters into the URL query string (on the /items
15
20
  // path) so a filtered view is shareable and bookmarkable. Replaces the current
16
21
  // history entry to avoid polluting back/forward with every keystroke.
@@ -206,8 +211,8 @@ export async function previewBulkUpdate() {
206
211
  payload.dryRun = 'true';
207
212
  try {
208
213
  const data = await api('POST', `/projects/${pid}/pm/update-many`, payload);
209
- const matched = data.item_plans || data.items || data.matched || [];
210
- const count = data.matched_count ?? data.count ?? data.total ?? matched.length;
214
+ const matched = data.item_plans ?? [];
215
+ const count = data.matched_count ?? matched.length;
211
216
  const applyBtn = document.getElementById('bu-apply-btn');
212
217
  if (previewEl) {
213
218
  const updateParts = [];
@@ -232,7 +237,7 @@ export async function previewBulkUpdate() {
232
237
  previewEl.innerHTML = `
233
238
  <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">
234
239
  <div style="margin-bottom:8px">Will update <strong>${displayCount}</strong> item${displayCount !== 1 ? 's' : ''}: ${updateDesc}</div>
235
- ${matched.slice(0, 8).map((it) => `<div style="color:var(--text-secondary);font-size:12px">· ${escHtml(it.id || '')} ${escHtml(it.title || '')}</div>`).join('')}
240
+ ${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('')}
236
241
  ${displayCount > 8 ? `<div style="color:var(--text-muted);font-size:12px;margin-top:4px">… and ${displayCount - 8} more</div>` : ''}
237
242
  </div>`;
238
243
  if (applyBtn)
@@ -240,7 +245,8 @@ export async function previewBulkUpdate() {
240
245
  // Store payload for apply (without dryRun)
241
246
  const applyPayload = { ...payload };
242
247
  delete applyPayload.dryRun;
243
- applyBtn._bulkPayload = applyPayload;
248
+ if (applyBtn)
249
+ bulkUpdatePayloads.set(applyBtn, applyPayload);
244
250
  }
245
251
  }
246
252
  }
@@ -254,7 +260,7 @@ export async function applyBulkUpdate() {
254
260
  if (!pid)
255
261
  return;
256
262
  const applyBtn = document.getElementById('bu-apply-btn');
257
- const payload = applyBtn?._bulkPayload;
263
+ const payload = applyBtn ? bulkUpdatePayloads.get(applyBtn) : undefined;
258
264
  if (!payload) {
259
265
  toast('Run Preview first', 'info');
260
266
  return;
@@ -265,7 +271,7 @@ export async function applyBulkUpdate() {
265
271
  }
266
272
  try {
267
273
  const data = await api('POST', `/projects/${pid}/pm/update-many`, payload);
268
- const updated = data.updated_count ?? data.updated ?? data.count ?? data.total ?? 'some';
274
+ const updated = data.updated_count ?? 'some';
269
275
  const failed = data.failed_count ?? 0;
270
276
  if (failed > 0) {
271
277
  toast(`Updated ${updated} item${updated !== 1 ? 's' : ''} (${failed} failed)`, 'info');
@@ -386,8 +392,8 @@ export async function previewBulkClose() {
386
392
  if (fAssignee)
387
393
  previewPayload.filterAssignee = fAssignee;
388
394
  const data = await api('POST', `/projects/${pid}/pm/update-many`, previewPayload);
389
- const matched = data.item_plans || data.items || data.matched || [];
390
- const count = data.matched_count ?? data.count ?? data.total ?? matched.length;
395
+ const matched = data.item_plans ?? [];
396
+ const count = data.matched_count ?? matched.length;
391
397
  const applyBtn = document.getElementById('bc-apply-btn');
392
398
  if (previewEl) {
393
399
  if (count === 0 && matched.length === 0) {
@@ -400,12 +406,12 @@ export async function previewBulkClose() {
400
406
  previewEl.innerHTML = `
401
407
  <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">
402
408
  <div style="margin-bottom:8px">Will <strong>${targetStatus}</strong> <strong>${displayCount}</strong> item${displayCount !== 1 ? 's' : ''}. Reason: <em>${escHtml(reason)}</em></div>
403
- ${matched.slice(0, 8).map((it) => `<div style="color:var(--text-secondary);font-size:12px">· ${escHtml(it.id || '')} ${escHtml(it.title || '')}</div>`).join('')}
409
+ ${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('')}
404
410
  ${displayCount > 8 ? `<div style="color:var(--text-muted);font-size:12px;margin-top:4px">… and ${displayCount - 8} more</div>` : ''}
405
411
  </div>`;
406
412
  if (applyBtn) {
407
413
  applyBtn.disabled = false;
408
- applyBtn._bulkClosePayload = { fStatus, fType, fSprint, fAssignee, targetStatus, reason };
414
+ bulkClosePayloads.set(applyBtn, { fStatus, fType, fSprint, fAssignee, targetStatus, reason });
409
415
  }
410
416
  }
411
417
  }
@@ -420,7 +426,7 @@ export async function applyBulkClose() {
420
426
  if (!pid)
421
427
  return;
422
428
  const applyBtn = document.getElementById('bc-apply-btn');
423
- const bulkClosePayload = applyBtn?._bulkClosePayload;
429
+ const bulkClosePayload = applyBtn ? bulkClosePayloads.get(applyBtn) : undefined;
424
430
  if (!bulkClosePayload) {
425
431
  toast('Run Preview first', 'info');
426
432
  return;
@@ -704,7 +710,7 @@ export async function openItemDetail(itemId) {
704
710
  <div class="item-detail-title">${escHtml(item.title)}</div>
705
711
  <div class="item-detail-meta">
706
712
  ${statusBadge(item.status || 'draft')}
707
- <div class="meta-chip">${priorityDot(item.priority ?? 4)} <strong>P${item.priority}</strong> ${PRIORITY_LABELS[item.priority] || ''}</div>
713
+ <div class="meta-chip">${priorityDot(item.priority ?? 4)} <strong>P${item.priority ?? '—'}</strong> ${item.priority !== undefined ? (PRIORITY_LABELS[item.priority] || '') : ''}</div>
708
714
  ${item.created_at ? `<div class="meta-chip">Created <strong>${fmtDate(item.created_at)}</strong></div>` : ''}
709
715
  ${item.updated_at ? `<div class="meta-chip">Updated <strong>${relTime(item.updated_at)}</strong></div>` : ''}
710
716
  ${item.parent ? `<div class="meta-chip">Parent <strong class="mono">${escHtml(item.parent)}</strong></div>` : ''}