@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.
- package/CHANGELOG.md +12 -0
- package/dist/index.js +1 -1
- package/dist/oidc.js.map +1 -1
- package/dist/routes/extensions.d.ts +1 -1
- package/dist/routes/oidc.d.ts +1 -1
- package/dist/routes/pm.js +125 -0
- package/dist/routes/pm.js.map +1 -1
- package/dist/services/mutation-event-watcher.d.ts +1 -1
- package/dist/services/package-catalog.d.ts +28 -4
- package/dist/services/package-catalog.js +67 -5
- package/dist/services/package-catalog.js.map +1 -1
- package/dist/services/pm-runner.d.ts +1 -1
- package/dist/services/pm-runner.js +8 -1
- package/dist/services/pm-runner.js.map +1 -1
- package/dist/services/project-watcher.d.ts +1 -1
- package/dist/services/realtime-bus.d.ts +1 -1
- package/manifest.json +1 -1
- package/package.json +21 -6
- package/public/src/api-types.js +17 -0
- package/public/src/api-types.js.map +1 -0
- package/public/src/api-types.ts +618 -0
- package/public/src/api.js +6 -3
- package/public/src/api.js.map +1 -1
- package/public/src/api.ts +30 -10
- package/public/src/app.js +4 -4
- package/public/src/app.js.map +1 -1
- package/public/src/app.ts +47 -12
- package/public/src/i18n/de.json +2 -1
- package/public/src/i18n/en.json +2 -1
- package/public/src/i18n/es.json +2 -1
- package/public/src/i18n/zh.json +2 -1
- package/public/src/i18n.ts +1 -1
- package/public/src/sw.ts +1 -0
- package/public/src/types.ts +11 -2
- package/public/src/views/activity.js +1 -1
- package/public/src/views/activity.js.map +1 -1
- package/public/src/views/activity.ts +5 -4
- package/public/src/views/calendar.js +1 -1
- package/public/src/views/calendar.js.map +1 -1
- package/public/src/views/calendar.ts +13 -11
- package/public/src/views/comments-audit.js.map +1 -1
- package/public/src/views/comments-audit.ts +7 -6
- package/public/src/views/config.js +1 -1
- package/public/src/views/config.js.map +1 -1
- package/public/src/views/config.ts +2 -2
- package/public/src/views/context.js +3 -3
- package/public/src/views/context.js.map +1 -1
- package/public/src/views/context.ts +10 -9
- package/public/src/views/create.js.map +1 -1
- package/public/src/views/create.ts +3 -2
- package/public/src/views/dedupe.js +5 -5
- package/public/src/views/dedupe.js.map +1 -1
- package/public/src/views/dedupe.ts +9 -8
- package/public/src/views/export.js.map +1 -1
- package/public/src/views/export.ts +2 -2
- package/public/src/views/github.js +5 -4
- package/public/src/views/github.js.map +1 -1
- package/public/src/views/github.ts +20 -18
- package/public/src/views/graph.js +7 -9
- package/public/src/views/graph.js.map +1 -1
- package/public/src/views/graph.ts +11 -13
- package/public/src/views/groups.js +1 -1
- package/public/src/views/groups.js.map +1 -1
- package/public/src/views/groups.ts +9 -8
- package/public/src/views/health.js +16 -2
- package/public/src/views/health.js.map +1 -1
- package/public/src/views/health.ts +26 -8
- package/public/src/views/items.js +18 -12
- package/public/src/views/items.js.map +1 -1
- package/public/src/views/items.ts +65 -35
- package/public/src/views/normalize.js.map +1 -1
- package/public/src/views/normalize.ts +5 -4
- package/public/src/views/packages.js +10 -1
- package/public/src/views/packages.js.map +1 -1
- package/public/src/views/packages.ts +19 -2
- package/public/src/views/plan.js +0 -2
- package/public/src/views/plan.js.map +1 -1
- package/public/src/views/plan.ts +5 -6
- package/public/src/views/projects.js.map +1 -1
- package/public/src/views/projects.ts +8 -7
- package/public/src/views/search.js.map +1 -1
- package/public/src/views/search.ts +3 -2
- package/public/src/views/settings.js.map +1 -1
- package/public/src/views/settings.ts +8 -6
- package/public/src/views/shared.js.map +1 -1
- package/public/src/views/shared.ts +4 -3
- package/public/src/views/sharing.js.map +1 -1
- package/public/src/views/sharing.ts +5 -4
- package/public/src/views/stats.js +1 -1
- package/public/src/views/stats.js.map +1 -1
- package/public/src/views/stats.ts +10 -9
- package/public/src/views/templates.js.map +1 -1
- package/public/src/views/templates.ts +6 -5
- package/public/src/views/validate.js.map +1 -1
- package/public/src/views/validate.ts +6 -5
- 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', () =>
|
|
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
|
-
|
|
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)
|
|
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 =
|
|
1215
|
-
if (kh) { document.removeEventListener('keydown', kh); delete
|
|
1216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1856
|
-
appw.__app?.openItemDetail(id);
|
|
1855
|
+
window.__app?.openItemDetail(id);
|
|
1857
1856
|
}
|
|
1858
1857
|
}
|
|
1859
1858
|
},
|
|
1860
1859
|
onOpenNode(id) {
|
|
1861
|
-
|
|
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.
|
|
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":"AAIA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;
|
|
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 =
|
|
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
|
|
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.
|
|
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 =
|
|
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
|
|
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(
|
|
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":"AAAA,kEAAkE;AAClE,cAAc;AACd,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;
|
|
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:
|
|
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:
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
210
|
-
const count = data.matched_count ??
|
|
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
|
|
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
|
|
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
|
|
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 ??
|
|
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
|
|
390
|
-
const count = data.matched_count ??
|
|
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
|
|
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
|
|
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
|
|
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>` : ''}
|