@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.
- package/CHANGELOG.md +53 -0
- package/README.md +1 -2
- package/dist/app.js +2 -0
- package/dist/app.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/oidc.js.map +1 -1
- package/dist/routes/extensions.d.ts +16 -0
- package/dist/routes/extensions.js +214 -0
- package/dist/routes/extensions.js.map +1 -0
- 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 +108 -0
- package/dist/services/package-catalog.js +256 -0
- package/dist/services/package-catalog.js.map +1 -0
- package/dist/services/pm-runner.d.ts +66 -1
- package/dist/services/pm-runner.js +83 -47
- 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 +24 -11
- package/public/index.html +4 -0
- 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 +11 -4
- package/public/src/app.js.map +1 -1
- package/public/src/app.ts +55 -12
- package/public/src/constants.js +1 -1
- package/public/src/constants.js.map +1 -1
- package/public/src/constants.ts +1 -1
- package/public/src/i18n/de.json +25 -1
- package/public/src/i18n/en.json +25 -1
- package/public/src/i18n/es.json +25 -1
- package/public/src/i18n/zh.json +26 -2
- package/public/src/i18n.ts +1 -1
- package/public/src/sw.ts +2 -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 +230 -0
- package/public/src/views/packages.js.map +1 -0
- package/public/src/views/packages.ts +255 -0
- 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/router.js +5 -0
- package/public/src/views/router.js.map +1 -1
- package/public/src/views/router.ts +3 -0
- 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 +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:
|
|
220
|
-
const count =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
264
|
-
const failed =
|
|
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:
|
|
371
|
-
const count =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
418
|
-
const failed =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
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 =
|
|
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
|
|
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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"}
|