@unbrained/pm-web 2026.8.10 → 2026.8.17
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 +22 -0
- package/dist/app.d.ts +16 -0
- package/dist/app.js +16 -0
- package/dist/app.js.map +1 -1
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js.map +1 -1
- package/dist/board.d.ts +53 -0
- package/dist/board.js +48 -0
- package/dist/board.js.map +1 -1
- package/dist/crypto.d.ts +23 -0
- package/dist/crypto.js +32 -0
- package/dist/crypto.js.map +1 -1
- package/dist/db.d.ts +19 -0
- package/dist/db.js +19 -0
- package/dist/db.js.map +1 -1
- package/dist/ical.d.ts +72 -0
- package/dist/ical.js +73 -0
- package/dist/ical.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +52 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts +8 -0
- package/dist/middleware/auth.js.map +1 -1
- package/dist/oidc.d.ts +87 -0
- package/dist/oidc.js +113 -0
- package/dist/oidc.js.map +1 -1
- package/dist/routes/admin.js +15 -0
- package/dist/routes/admin.js.map +1 -1
- package/dist/routes/extensions.js +10 -0
- package/dist/routes/extensions.js.map +1 -1
- package/dist/routes/github.js +13 -0
- package/dist/routes/github.js.map +1 -1
- package/dist/routes/oidc.d.ts +10 -0
- package/dist/routes/oidc.js +51 -0
- package/dist/routes/oidc.js.map +1 -1
- package/dist/routes/pm.js +132 -1
- package/dist/routes/pm.js.map +1 -1
- package/dist/routes/sharing.js +8 -0
- package/dist/routes/sharing.js.map +1 -1
- package/dist/services/mutation-event-watcher.js +13 -0
- package/dist/services/mutation-event-watcher.js.map +1 -1
- package/dist/services/package-catalog.d.ts +24 -0
- package/dist/services/package-catalog.js +22 -2
- package/dist/services/package-catalog.js.map +1 -1
- package/dist/services/pm-runner.d.ts +82 -0
- package/dist/services/pm-runner.js +121 -0
- package/dist/services/pm-runner.js.map +1 -1
- package/dist/services/project-watcher.d.ts +22 -0
- package/dist/services/project-watcher.js +51 -0
- package/dist/services/project-watcher.js.map +1 -1
- package/dist/services/realtime-bus.d.ts +46 -0
- package/dist/services/realtime-bus.js +59 -0
- package/dist/services/realtime-bus.js.map +1 -1
- package/dist/services/sse.d.ts +134 -0
- package/dist/services/sse.js +145 -0
- package/dist/services/sse.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +5 -3
- package/public/src/app.js +44 -1
- package/public/src/app.js.map +1 -1
- package/public/src/app.ts +44 -1
- package/public/src/components/modals.js +11 -0
- package/public/src/components/modals.js.map +1 -1
- package/public/src/components/modals.ts +11 -0
- package/public/src/components/toast.js +2 -0
- package/public/src/components/toast.js.map +1 -1
- package/public/src/components/toast.ts +2 -0
- package/public/src/constants.js +21 -4
- package/public/src/constants.js.map +1 -1
- package/public/src/constants.ts +21 -4
- package/public/src/filters.js +10 -7
- package/public/src/filters.js.map +1 -1
- package/public/src/filters.ts +10 -7
- package/public/src/i18n/de.json +3 -1
- package/public/src/i18n/en.json +3 -1
- package/public/src/i18n/es.json +3 -1
- package/public/src/i18n/zh.json +3 -1
- package/public/src/i18n.js +11 -1
- package/public/src/i18n.js.map +1 -1
- package/public/src/i18n.ts +13 -1
- package/public/src/state.js +3 -0
- package/public/src/state.js.map +1 -1
- package/public/src/state.ts +3 -0
- package/public/src/sw.ts +71 -7
- package/public/src/theme.js +13 -1
- package/public/src/theme.js.map +1 -1
- package/public/src/theme.ts +15 -1
- package/public/src/types.ts +2 -0
- package/public/src/utils.js +26 -1
- package/public/src/utils.js.map +1 -1
- package/public/src/utils.ts +26 -1
- package/public/src/views/activity.js +1 -0
- package/public/src/views/activity.js.map +1 -1
- package/public/src/views/activity.ts +1 -0
- package/public/src/views/admin.js +134 -0
- package/public/src/views/admin.js.map +1 -1
- package/public/src/views/admin.ts +134 -0
- package/public/src/views/auth.js +31 -0
- package/public/src/views/auth.js.map +1 -1
- package/public/src/views/auth.ts +31 -0
- package/public/src/views/calendar.js +3 -0
- package/public/src/views/calendar.js.map +1 -1
- package/public/src/views/calendar.ts +3 -0
- package/public/src/views/comments-audit.js +1 -0
- package/public/src/views/comments-audit.js.map +1 -1
- package/public/src/views/comments-audit.ts +1 -0
- package/public/src/views/config.js +67 -0
- package/public/src/views/config.js.map +1 -1
- package/public/src/views/config.ts +67 -0
- package/public/src/views/context.js +2 -0
- package/public/src/views/context.js.map +1 -1
- package/public/src/views/context.ts +2 -0
- package/public/src/views/create.js +3 -0
- package/public/src/views/create.js.map +1 -1
- package/public/src/views/create.ts +3 -0
- package/public/src/views/dedupe.js +1 -0
- package/public/src/views/dedupe.js.map +1 -1
- package/public/src/views/dedupe.ts +1 -0
- package/public/src/views/export.js +6 -0
- package/public/src/views/export.js.map +1 -1
- package/public/src/views/export.ts +6 -0
- package/public/src/views/github.js +58 -0
- package/public/src/views/github.js.map +1 -1
- package/public/src/views/github.ts +58 -0
- package/public/src/views/graph-canvas.js +18 -0
- package/public/src/views/graph-canvas.js.map +1 -1
- package/public/src/views/graph-canvas.ts +20 -0
- package/public/src/views/graph.js +41 -0
- package/public/src/views/graph.js.map +1 -1
- package/public/src/views/graph.ts +42 -1
- package/public/src/views/groups.js +45 -0
- package/public/src/views/groups.js.map +1 -1
- package/public/src/views/groups.ts +45 -0
- package/public/src/views/guide.js +3 -0
- package/public/src/views/guide.js.map +1 -1
- package/public/src/views/guide.ts +3 -0
- package/public/src/views/health.js +3 -0
- package/public/src/views/health.js.map +1 -1
- package/public/src/views/health.ts +3 -0
- package/public/src/views/items.js +196 -6
- package/public/src/views/items.js.map +1 -1
- package/public/src/views/items.ts +196 -6
- package/public/src/views/normalize.js +2 -0
- package/public/src/views/normalize.js.map +1 -1
- package/public/src/views/normalize.ts +2 -0
- package/public/src/views/packages.js +67 -5
- package/public/src/views/packages.js.map +1 -1
- package/public/src/views/packages.ts +74 -5
- package/public/src/views/plan-execution.js +6 -0
- package/public/src/views/plan-execution.js.map +1 -1
- package/public/src/views/plan-execution.ts +8 -0
- package/public/src/views/plan.js +145 -0
- package/public/src/views/plan.js.map +1 -1
- package/public/src/views/plan.ts +146 -1
- package/public/src/views/projects.js +60 -0
- package/public/src/views/projects.js.map +1 -1
- package/public/src/views/projects.ts +60 -0
- package/public/src/views/router.js +43 -0
- package/public/src/views/router.js.map +1 -1
- package/public/src/views/router.ts +43 -0
- package/public/src/views/search.js +5 -0
- package/public/src/views/search.js.map +1 -1
- package/public/src/views/search.ts +5 -0
- package/public/src/views/settings.js +36 -0
- package/public/src/views/settings.js.map +1 -1
- package/public/src/views/settings.ts +36 -0
- package/public/src/views/shared.js +1 -0
- package/public/src/views/shared.js.map +1 -1
- package/public/src/views/shared.ts +1 -0
- package/public/src/views/sharing.js +32 -0
- package/public/src/views/sharing.js.map +1 -1
- package/public/src/views/sharing.ts +32 -0
- package/public/src/views/stats.js +1 -0
- package/public/src/views/stats.js.map +1 -1
- package/public/src/views/stats.ts +1 -0
- package/public/src/views/templates.js +4 -0
- package/public/src/views/templates.js.map +1 -1
- package/public/src/views/templates.ts +4 -0
- package/public/src/views/validate.js +1 -0
- package/public/src/views/validate.js.map +1 -1
- package/public/src/views/validate.ts +1 -0
- package/public/sw.js +60 -5
|
@@ -75,6 +75,7 @@ function removeCtxMenu(): void {
|
|
|
75
75
|
if (ctxMenuEl) { ctxMenuEl.remove(); ctxMenuEl = null; }
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/** Builds and shows the right-click context menu for a graph node, with actions to open the item, select and focus, show its neighborhood, expand two hops, and copy its id. */
|
|
78
79
|
function showCtxMenu(nodeId: string, x: number, y: number): void {
|
|
79
80
|
removeCtxMenu();
|
|
80
81
|
const graph = currentGraph?.graph || {};
|
|
@@ -149,6 +150,7 @@ function nodeTitle(node: GraphNode): string {
|
|
|
149
150
|
return String(node.properties?.title || node.id);
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
/** Returns a display type for a node, preferring the `kind` property, then `type`, then a non-core label, defaulting to 'Item'. */
|
|
152
154
|
function nodeType(node: GraphNode): string {
|
|
153
155
|
return String(
|
|
154
156
|
node.properties?.kind
|
|
@@ -176,6 +178,7 @@ function nodeLane(node: GraphNode): CanvasNode['lane'] {
|
|
|
176
178
|
return 'item';
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
/** Extracts a short user-facing error message from a raw error string (JSON detail, title, or code, otherwise the trimmed raw text), returning an empty string when the error is just the missing pm-graph extension. */
|
|
179
182
|
function compactError(raw: string | undefined): string {
|
|
180
183
|
if (!raw) return '';
|
|
181
184
|
try {
|
|
@@ -190,6 +193,7 @@ function compactError(raw: string | undefined): string {
|
|
|
190
193
|
|
|
191
194
|
// ── Graph data processing ─────────────────────────────────────
|
|
192
195
|
|
|
196
|
+
/** Returns the set of ids directly connected to the given node (plus the node itself) across the provided relationships. */
|
|
193
197
|
function directNeighborIds(nodeId: string, rels: GraphRelationship[]): Set<string> {
|
|
194
198
|
const ids = new Set<string>([nodeId]);
|
|
195
199
|
for (const r of rels) {
|
|
@@ -199,6 +203,7 @@ function directNeighborIds(nodeId: string, rels: GraphRelationship[]): Set<strin
|
|
|
199
203
|
return ids;
|
|
200
204
|
}
|
|
201
205
|
|
|
206
|
+
/** Returns the neighbor id(s) on the other end of a relationship relative to a node, filtered by traversal direction (incoming, outgoing, or connected). */
|
|
202
207
|
function walkDirectionMatch(rel: GraphRelationship, nodeId: string, dir: GraphFilter['direction']): string[] {
|
|
203
208
|
if (dir === 'incoming') {
|
|
204
209
|
return rel.to === nodeId ? [rel.from] : [];
|
|
@@ -209,6 +214,7 @@ function walkDirectionMatch(rel: GraphRelationship, nodeId: string, dir: GraphFi
|
|
|
209
214
|
return rel.to === nodeId ? [rel.from] : rel.from === nodeId ? [rel.to] : [];
|
|
210
215
|
}
|
|
211
216
|
|
|
217
|
+
/** Performs a bounded breadth-first traversal out to `depth` hops from a node, returning all reachable ids (including the node itself) while respecting the traversal direction. */
|
|
212
218
|
function expandedNeighborIds(nodeId: string, rels: GraphRelationship[], depth: number, direction: GraphFilter['direction']): Set<string> {
|
|
213
219
|
const ids = new Set<string>([nodeId]);
|
|
214
220
|
let frontier = new Set<string>([nodeId]);
|
|
@@ -229,6 +235,7 @@ function expandedNeighborIds(nodeId: string, rels: GraphRelationship[], depth: n
|
|
|
229
235
|
return ids;
|
|
230
236
|
}
|
|
231
237
|
|
|
238
|
+
/** Counts both endpoints of every relationship, returning a map of node id to its total edge degree (incrementing the count for both `from` and `to`). */
|
|
232
239
|
function degreeMap(rels: GraphRelationship[]): Map<string, number> {
|
|
233
240
|
const m = new Map<string, number>();
|
|
234
241
|
for (const r of rels) {
|
|
@@ -248,6 +255,7 @@ function blockingPair(rel: GraphRelationship): { blocked: string; blocker: strin
|
|
|
248
255
|
return null;
|
|
249
256
|
}
|
|
250
257
|
|
|
258
|
+
/** Maps a relationship type to a human-readable dependency label (for example 'Depends on'), falling back to the lowercased type with underscores replaced by spaces. */
|
|
251
259
|
function dependencyLabel(rel: GraphRelationship): string {
|
|
252
260
|
const labels: Record<string, string> = {
|
|
253
261
|
DEPENDS_ON: 'Depends on',
|
|
@@ -263,6 +271,7 @@ function dependencyLabel(rel: GraphRelationship): string {
|
|
|
263
271
|
return labels[rel.type] ?? rel.type.replace(/_/g, ' ').toLowerCase();
|
|
264
272
|
}
|
|
265
273
|
|
|
274
|
+
/** Computes the critical (longest) dependency path through the given relationships and returns its node ids as a set, or an empty set when there is no multi-step chain. */
|
|
266
275
|
function computeCriticalPath(rels: GraphRelationship[]): Set<string> {
|
|
267
276
|
const depRels = rels.filter(isDependencyRel);
|
|
268
277
|
if (!depRels.length) return new Set();
|
|
@@ -279,6 +288,7 @@ function computeCriticalPath(rels: GraphRelationship[]): Set<string> {
|
|
|
279
288
|
}
|
|
280
289
|
|
|
281
290
|
const memo = new Map<string, string[]>();
|
|
291
|
+
/** Memoized depth-first search returning the longest chain of blockers starting from the given id, guarding against cycles with a visited set. */
|
|
282
292
|
function longestPathFrom(id: string, seen = new Set<string>()): string[] {
|
|
283
293
|
if (memo.has(id)) return memo.get(id)!;
|
|
284
294
|
if (seen.has(id)) return [id];
|
|
@@ -303,6 +313,7 @@ function computeCriticalPath(rels: GraphRelationship[]): Set<string> {
|
|
|
303
313
|
return longest.length < 2 ? new Set() : new Set(longest);
|
|
304
314
|
}
|
|
305
315
|
|
|
316
|
+
/** Builds per-node dependency stats from blocking relationships, mapping each node id to the sets of items that block it and that it blocks. */
|
|
306
317
|
function blockerStats(rels: GraphRelationship[]): Map<string, { blockers: Set<string>; blocked: Set<string> }> {
|
|
307
318
|
const stats = new Map<string, { blockers: Set<string>; blocked: Set<string> }>();
|
|
308
319
|
const entry = (id: string) => {
|
|
@@ -318,6 +329,7 @@ function blockerStats(rels: GraphRelationship[]): Map<string, { blockers: Set<st
|
|
|
318
329
|
return stats;
|
|
319
330
|
}
|
|
320
331
|
|
|
332
|
+
/** Applies the current filters (dep mode, focus neighborhood, node kind, status, text query, relationship type, and direction) to a graph, returning the surviving nodes, relationships, and the set of all connected node ids. */
|
|
321
333
|
function visibleGraph(graph: ProjectGraph): { nodes: GraphNode[]; rels: GraphRelationship[]; connected: Set<string> } {
|
|
322
334
|
const nodes = graph.nodes || [];
|
|
323
335
|
let rels = graph.relationships || [];
|
|
@@ -369,6 +381,7 @@ function visibleGraph(graph: ProjectGraph): { nodes: GraphNode[]; rels: GraphRel
|
|
|
369
381
|
|
|
370
382
|
// ── Canvas data conversion ────────────────────────────────────
|
|
371
383
|
|
|
384
|
+
/** Converts graph nodes into canvas display nodes, attaching each node's edge degree (computed from the relationships) so the renderer can size them. */
|
|
372
385
|
function toCanvasNodes(nodes: GraphNode[], rels: GraphRelationship[]): CanvasNode[] {
|
|
373
386
|
const deg = degreeMap(rels);
|
|
374
387
|
return nodes.map((n) => ({
|
|
@@ -392,6 +405,7 @@ function toCanvasEdges(rels: GraphRelationship[]): CanvasEdge[] {
|
|
|
392
405
|
|
|
393
406
|
// ── Info panel rendering ──────────────────────────────────────
|
|
394
407
|
|
|
408
|
+
/** Renders the graph-coverage HTML: a linked-item percentage bar, the external cross-project reference count, and the top relationship type with its edge count. */
|
|
395
409
|
function renderCoverage(
|
|
396
410
|
itemNodes: GraphNode[],
|
|
397
411
|
rels: GraphRelationship[],
|
|
@@ -429,6 +443,7 @@ function renderCoverage(
|
|
|
429
443
|
</div>`;
|
|
430
444
|
}
|
|
431
445
|
|
|
446
|
+
/** Renders the selected-node HTML panel, including metadata, action buttons, blocker counts, the property grid, tags, an optional description, and its direct relationships. */
|
|
432
447
|
function renderSelectedNode(
|
|
433
448
|
node: GraphNode | undefined,
|
|
434
449
|
rels: GraphRelationship[],
|
|
@@ -502,6 +517,7 @@ function renderSelectedNode(
|
|
|
502
517
|
</div>`;
|
|
503
518
|
}
|
|
504
519
|
|
|
520
|
+
/** Renders the neighborhood HTML for the selected node, showing clickable one-hop and two-hop neighbor chips. */
|
|
505
521
|
function renderPaths(
|
|
506
522
|
node: GraphNode | undefined,
|
|
507
523
|
rels: GraphRelationship[],
|
|
@@ -525,6 +541,7 @@ function renderPaths(
|
|
|
525
541
|
<div class="graph-path-section"><div class="graph-path-label">Two hops (${twoHop.size})</div><div class="graph-path-chips">${chips(twoHop) || '<span>None.</span>'}</div></div>`;
|
|
526
542
|
}
|
|
527
543
|
|
|
544
|
+
/** Renders the item-hubs HTML, listing the top eight most-connected items by degree with their outgoing and incoming edge counts. */
|
|
528
545
|
function renderHubs(nodes: GraphNode[], rels: GraphRelationship[]): string {
|
|
529
546
|
const deg = degreeMap(rels);
|
|
530
547
|
const hubs = nodes.filter(isItemNode)
|
|
@@ -541,6 +558,7 @@ function renderHubs(nodes: GraphNode[], rels: GraphRelationship[]): string {
|
|
|
541
558
|
</button>`).join('');
|
|
542
559
|
}
|
|
543
560
|
|
|
561
|
+
/** Renders the dependency-blocker HTML, listing the top eight items that block or are blocked by the most other items and flagging those on the critical path. */
|
|
544
562
|
function renderBlockingInsights(nodes: GraphNode[], rels: GraphRelationship[]): string {
|
|
545
563
|
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
546
564
|
const stats = blockerStats(rels);
|
|
@@ -558,6 +576,7 @@ function renderBlockingInsights(nodes: GraphNode[], rels: GraphRelationship[]):
|
|
|
558
576
|
</button>`).join('');
|
|
559
577
|
}
|
|
560
578
|
|
|
579
|
+
/** Renders the dependency-chains HTML: the longest dependency chains, potential cycle warnings, and root blockers, with critical-path highlighting. */
|
|
561
580
|
function renderDependencyChains(nodes: GraphNode[], rels: GraphRelationship[]): string {
|
|
562
581
|
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
563
582
|
const depRels = rels.filter(isDependencyRel);
|
|
@@ -581,6 +600,7 @@ function renderDependencyChains(nodes: GraphNode[], rels: GraphRelationship[]):
|
|
|
581
600
|
.sort((a, b) => b.blocked - a.blocked || nodeTitle(byId.get(a.id) || { id: a.id }).localeCompare(nodeTitle(byId.get(b.id) || { id: b.id })))
|
|
582
601
|
.slice(0, 4);
|
|
583
602
|
|
|
603
|
+
/** Depth-first trace of a dependency chain from the given id, recording detected cycle starts and returning the longest chain of blockers found. */
|
|
584
604
|
function traceFrom(id: string, seen = new Set<string>()): string[] {
|
|
585
605
|
if (seen.has(id)) {
|
|
586
606
|
cycleStarts.add(id);
|
|
@@ -631,6 +651,7 @@ function renderDependencyChains(nodes: GraphNode[], rels: GraphRelationship[]):
|
|
|
631
651
|
</div>`;
|
|
632
652
|
}
|
|
633
653
|
|
|
654
|
+
/** Renders the full graph-analysis info panel HTML by composing the coverage, selected node, neighborhood, hubs, blockers, dependency-chain, and node/relationship count sections. */
|
|
634
655
|
function renderInfoPanel(data: GraphResponse, fullItem?: Record<string, unknown>): string {
|
|
635
656
|
const graph = data.graph || {};
|
|
636
657
|
const nodes = graph.nodes || [];
|
|
@@ -670,6 +691,7 @@ function renderInfoPanel(data: GraphResponse, fullItem?: Record<string, unknown>
|
|
|
670
691
|
|
|
671
692
|
// ── Rel list (inside bottom drawer) ──────────────────────────
|
|
672
693
|
|
|
694
|
+
/** Renders the relationship list shown in the bottom drawer: add/remove dependency buttons and up to 100 filtered relationship rows, each linking its two endpoints. */
|
|
673
695
|
function renderRelList(data: GraphResponse): string {
|
|
674
696
|
const graph = data.graph || {};
|
|
675
697
|
const nodes = graph.nodes || [];
|
|
@@ -708,6 +730,7 @@ function renderRelList(data: GraphResponse): string {
|
|
|
708
730
|
|
|
709
731
|
// ── Immersive shell ───────────────────────────────────────────
|
|
710
732
|
|
|
733
|
+
/** Returns whether the given preset id (knowledge, dependency, unlinked, metadata, critical, or tags) matches the current filter configuration. */
|
|
711
734
|
function graphPresetActive(id: string): boolean {
|
|
712
735
|
if (id === 'knowledge') {
|
|
713
736
|
return !filter.depMode && (filter.kind === 'items' || filter.kind === 'all') && filter.rel === 'all' && filter.scope === 'all' && filter.colorMode === 'status';
|
|
@@ -720,6 +743,7 @@ function graphPresetActive(id: string): boolean {
|
|
|
720
743
|
return false;
|
|
721
744
|
}
|
|
722
745
|
|
|
746
|
+
/** Renders the preset rail toolbar HTML, with one button per view preset (Knowledge, Dependencies, Unlinked, Tags, Metadata, Critical) showing live counts and active state. */
|
|
723
747
|
function renderGraphPresets(depRels: GraphRelationship[], isolatedCount: number): string {
|
|
724
748
|
const allNodes = currentGraph?.graph?.nodes ?? [];
|
|
725
749
|
const tagSet = new Set<string>();
|
|
@@ -745,6 +769,7 @@ function renderGraphPresets(depRels: GraphRelationship[], isolatedCount: number)
|
|
|
745
769
|
</div>`;
|
|
746
770
|
}
|
|
747
771
|
|
|
772
|
+
/** Renders the full immersive graph shell HTML: the canvas host, the top HUD bar with search and presets, physics controls, filter overlay, legend, info drawer, and relationship drawer. */
|
|
748
773
|
function renderGraphShell(data: GraphResponse): string {
|
|
749
774
|
const graph = data.graph || {};
|
|
750
775
|
const nodes = graph.nodes || [];
|
|
@@ -945,6 +970,7 @@ function renderGraphShell(data: GraphResponse): string {
|
|
|
945
970
|
|
|
946
971
|
// ── Canvas init / update ──────────────────────────────────────
|
|
947
972
|
|
|
973
|
+
/** Fetches the full item record for the selected item-lane node and re-renders the info panel with its description; clears the cached item for non-item nodes or on failure. */
|
|
948
974
|
async function fetchAndUpdateSelectedItem(nodeId: string): Promise<void> {
|
|
949
975
|
if (!state.currentProject || !nodeId) return;
|
|
950
976
|
// Only fetch for item-lane nodes (not facets)
|
|
@@ -968,6 +994,7 @@ async function fetchAndUpdateSelectedItem(nodeId: string): Promise<void> {
|
|
|
968
994
|
}
|
|
969
995
|
}
|
|
970
996
|
|
|
997
|
+
/** Pushes the current filters and selection onto the canvas and, when searching with no node selected, jumps the camera to the first matching node. */
|
|
971
998
|
function syncCanvas(): void {
|
|
972
999
|
if (!canvasRef.current || !currentGraph) return;
|
|
973
1000
|
const graph = currentGraph.graph || {};
|
|
@@ -994,6 +1021,7 @@ function syncCanvas(): void {
|
|
|
994
1021
|
}
|
|
995
1022
|
}
|
|
996
1023
|
|
|
1024
|
+
/** Creates the GraphCanvas instance for the current graph, wires its selection, open, and context-menu callbacks, computes the critical path, loads the data, and applies the initial filter. */
|
|
997
1025
|
function initCanvas(): void {
|
|
998
1026
|
const host = document.getElementById('graph-canvas-host') as HTMLElement | null;
|
|
999
1027
|
if (!host || !currentGraph) return;
|
|
@@ -1036,6 +1064,7 @@ function initCanvas(): void {
|
|
|
1036
1064
|
|
|
1037
1065
|
// ── Panel / drawer updates ────────────────────────────────────
|
|
1038
1066
|
|
|
1067
|
+
/** Re-renders the info panel and relationship list into the DOM from the current graph (and cached selected item) and re-binds their event handlers. */
|
|
1039
1068
|
function updateInfoPanel(): void {
|
|
1040
1069
|
if (!currentGraph) return;
|
|
1041
1070
|
const panel = document.getElementById('graph-info-panel');
|
|
@@ -1045,6 +1074,7 @@ function updateInfoPanel(): void {
|
|
|
1045
1074
|
bindInfoPanelEvents();
|
|
1046
1075
|
}
|
|
1047
1076
|
|
|
1077
|
+
/** Synchronizes the filter toolbar DOM (direction select enabled state, depth slider and label, scope button text, and preset active states) with the current selection and filter. */
|
|
1048
1078
|
function updateFilterToolbarState(): void {
|
|
1049
1079
|
const dirSel = document.getElementById('graph-filter-direction') as HTMLSelectElement | null;
|
|
1050
1080
|
const depthSldr = document.getElementById('graph-filter-depth') as HTMLInputElement | null;
|
|
@@ -1067,6 +1097,7 @@ const TYPE_COLORS_MAP: Record<string, string> = {
|
|
|
1067
1097
|
};
|
|
1068
1098
|
const TAG_PALETTE_JS = ['#2dd4bf','#60a5fa','#a78bfa','#f87171','#fbbf24','#34d399','#fb923c','#e879f9'];
|
|
1069
1099
|
|
|
1100
|
+
/** 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. */
|
|
1070
1101
|
function computeTagColorMap(nodes: GraphNode[]): Map<string, string> {
|
|
1071
1102
|
const freq = new Map<string, number>();
|
|
1072
1103
|
for (const n of nodes) {
|
|
@@ -1077,6 +1108,7 @@ function computeTagColorMap(nodes: GraphNode[]): Map<string, string> {
|
|
|
1077
1108
|
return new Map(top.map((t, i) => [t, TAG_PALETTE_JS[i]]));
|
|
1078
1109
|
}
|
|
1079
1110
|
|
|
1111
|
+
/** 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. */
|
|
1080
1112
|
function updateLegend(): void {
|
|
1081
1113
|
const legend = document.getElementById('graph-legend-hud');
|
|
1082
1114
|
if (!legend) return;
|
|
@@ -1150,6 +1182,7 @@ function updateLegend(): void {
|
|
|
1150
1182
|
|
|
1151
1183
|
// ── Event bindings ────────────────────────────────────────────
|
|
1152
1184
|
|
|
1185
|
+
/** 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. */
|
|
1153
1186
|
function bindInfoPanelEvents(): void {
|
|
1154
1187
|
document.getElementById('graph-open-selected')?.addEventListener('click', () => {
|
|
1155
1188
|
if (selectedNodeId) window.__app?.openItemDetail(selectedNodeId);
|
|
@@ -1206,6 +1239,7 @@ function bindInfoPanelEvents(): void {
|
|
|
1206
1239
|
});
|
|
1207
1240
|
}
|
|
1208
1241
|
|
|
1242
|
+
/** Binds all HUD control handlers for the graph view: back/refresh/sync/fit buttons, presets, physics toggles and sliders, filter selects, search input, drawer toggles, color mode, and global keyboard shortcuts. */
|
|
1209
1243
|
function bindHudEvents(): void {
|
|
1210
1244
|
// Back button
|
|
1211
1245
|
document.getElementById('graph-back-btn')?.addEventListener('click', () => {
|
|
@@ -1543,6 +1577,7 @@ function bindHudEvents(): void {
|
|
|
1543
1577
|
|
|
1544
1578
|
// ── URL routing (pushState) ─────────────────────────────────
|
|
1545
1579
|
|
|
1580
|
+
/** Serializes the current selection and filter into URL query parameters and replaces the browser history entry so the view can be restored or shared. */
|
|
1546
1581
|
function pushGraphState(): void {
|
|
1547
1582
|
if (!state.currentProject) return;
|
|
1548
1583
|
const params = new URLSearchParams();
|
|
@@ -1561,6 +1596,7 @@ function pushGraphState(): void {
|
|
|
1561
1596
|
|
|
1562
1597
|
let urlStateRestored = false;
|
|
1563
1598
|
|
|
1599
|
+
/** Reads graph view parameters from the current URL query string and applies them to the selection and filter, marking state as restored. */
|
|
1564
1600
|
function restoreGraphState(): void {
|
|
1565
1601
|
const params = new URLSearchParams(window.location.search);
|
|
1566
1602
|
if (!params.has('graph')) return;
|
|
@@ -1575,6 +1611,7 @@ function restoreGraphState(): void {
|
|
|
1575
1611
|
|
|
1576
1612
|
// ── Dependency editing modals ────────────────────────────────
|
|
1577
1613
|
|
|
1614
|
+
/** Shows the add-dependency modal listing item nodes as source and target selects plus a relationship type, and POSTs the new dependency then refreshes the graph on submit. */
|
|
1578
1615
|
function showAddDependencyModal(): void {
|
|
1579
1616
|
if (!state.currentProject || !currentGraph) return;
|
|
1580
1617
|
const graph = currentGraph.graph || {};
|
|
@@ -1647,6 +1684,7 @@ function showAddDependencyModal(): void {
|
|
|
1647
1684
|
});
|
|
1648
1685
|
}
|
|
1649
1686
|
|
|
1687
|
+
/** Shows the remove-dependency modal listing existing dependency relationships and DELETEs the chosen one on submit, then refreshes the graph. */
|
|
1650
1688
|
function showRemoveDependencyModal(): void {
|
|
1651
1689
|
if (!state.currentProject || !currentGraph) return;
|
|
1652
1690
|
const graph = currentGraph.graph || {};
|
|
@@ -1713,6 +1751,7 @@ function showRemoveDependencyModal(): void {
|
|
|
1713
1751
|
|
|
1714
1752
|
// ── Main entry point ──────────────────────────────────────────
|
|
1715
1753
|
|
|
1754
|
+
/** Renders the full graph view into the `content-graph` element: clears any prior canvas, restores URL state, fetches the project graph, computes the critical path, renders the shell, binds events, and initializes the canvas. */
|
|
1716
1755
|
export async function renderGraphView(): Promise<void> {
|
|
1717
1756
|
const el = document.getElementById('content-graph');
|
|
1718
1757
|
if (!el) return;
|
|
@@ -1785,7 +1824,7 @@ export async function refreshGraphData(): Promise<void> {
|
|
|
1785
1824
|
updateInfoPanel();
|
|
1786
1825
|
syncCanvas();
|
|
1787
1826
|
} catch {
|
|
1788
|
-
|
|
1827
|
+
// Silently ignore — user can hit Refresh manually
|
|
1789
1828
|
}
|
|
1790
1829
|
}
|
|
1791
1830
|
|
|
@@ -1794,11 +1833,13 @@ export async function refreshGraphData(): Promise<void> {
|
|
|
1794
1833
|
// Registry of active local graph canvases so they can be cleaned up
|
|
1795
1834
|
const localGraphRegistry = new Map<string, GraphCanvas>();
|
|
1796
1835
|
|
|
1836
|
+
/** Destroys and unregisters the local mini-graph canvas previously created for the given container id, if any. */
|
|
1797
1837
|
export function destroyLocalGraph(containerId: string): void {
|
|
1798
1838
|
const existing = localGraphRegistry.get(containerId);
|
|
1799
1839
|
if (existing) { existing.destroy(); localGraphRegistry.delete(containerId); }
|
|
1800
1840
|
}
|
|
1801
1841
|
|
|
1842
|
+
/** Renders an embedded mini-graph for a single item showing its neighborhood out to the given depth, with neighbor-node clicks navigating to that item's detail view. */
|
|
1802
1843
|
export async function renderLocalGraph(
|
|
1803
1844
|
containerId: string,
|
|
1804
1845
|
nodeId: string,
|
|
@@ -2,6 +2,10 @@ import { api } from '../api.js';
|
|
|
2
2
|
import { escHtml } from '../utils.js';
|
|
3
3
|
import { showModal, hideModal, createModal, confirmDialog } from '../components/modals.js';
|
|
4
4
|
import { toast } from '../components/toast.js';
|
|
5
|
+
/**
|
|
6
|
+
* Renders the groups page header and a loading placeholder, then fetches and
|
|
7
|
+
* displays the list of teams.
|
|
8
|
+
*/
|
|
5
9
|
export async function renderGroupsView() {
|
|
6
10
|
const el = document.getElementById('content-groups');
|
|
7
11
|
if (!el)
|
|
@@ -17,6 +21,11 @@ export async function renderGroupsView() {
|
|
|
17
21
|
<div id="groups-list"><div class="loading-state"><div class="loading-spinner"></div></div></div>`;
|
|
18
22
|
await loadGroups();
|
|
19
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Fetches the user's teams from the API and renders each as a clickable row
|
|
26
|
+
* showing avatar initials, name, description, and member count, or an empty
|
|
27
|
+
* state when none exist.
|
|
28
|
+
*/
|
|
20
29
|
async function loadGroups() {
|
|
21
30
|
const el = document.getElementById('groups-list');
|
|
22
31
|
if (!el)
|
|
@@ -53,6 +62,10 @@ async function loadGroups() {
|
|
|
53
62
|
el.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
|
|
54
63
|
}
|
|
55
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Builds and displays the modal form for creating a new team, with name and
|
|
67
|
+
* description inputs and a validation slot.
|
|
68
|
+
*/
|
|
56
69
|
export function openCreateGroupModal() {
|
|
57
70
|
createModal('create-group-modal', 'New Group', `
|
|
58
71
|
<div class="form-group">
|
|
@@ -67,6 +80,10 @@ export function openCreateGroupModal() {
|
|
|
67
80
|
<button class="btn btn-primary" onclick="window.__app.submitCreateGroup()"><span>Create Group</span></button>`);
|
|
68
81
|
showModal('create-group-modal');
|
|
69
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Reads the create-team form, requires a name, posts the new team, and
|
|
85
|
+
* reloads the list on success, surfacing validation and server errors inline.
|
|
86
|
+
*/
|
|
70
87
|
export async function submitCreateGroup() {
|
|
71
88
|
const name = document.getElementById('cg-name')?.value?.trim() || '';
|
|
72
89
|
const description = document.getElementById('cg-desc')?.value?.trim() || '';
|
|
@@ -93,6 +110,13 @@ export async function submitCreateGroup() {
|
|
|
93
110
|
}
|
|
94
111
|
}
|
|
95
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Confirms and permanently deletes the named team, then refreshes the list,
|
|
115
|
+
* showing any errors as toasts.
|
|
116
|
+
*
|
|
117
|
+
* @param groupId - identifier of the team to delete.
|
|
118
|
+
* @param name - display name shown in the confirmation prompt.
|
|
119
|
+
*/
|
|
96
120
|
export function deleteGroup(groupId, name) {
|
|
97
121
|
confirmDialog('Delete Group?', `Delete group "${name}"? This cannot be undone.`, async () => {
|
|
98
122
|
try {
|
|
@@ -105,6 +129,14 @@ export function deleteGroup(groupId, name) {
|
|
|
105
129
|
}
|
|
106
130
|
}, true);
|
|
107
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Opens a modal showing a team's members with an invite-by-email field,
|
|
134
|
+
* fetching the membership from the API and replacing the loading placeholder
|
|
135
|
+
* with the rendered list.
|
|
136
|
+
*
|
|
137
|
+
* @param groupId - identifier of the team to display.
|
|
138
|
+
* @param _groupName - fallback title used before the API response resolves.
|
|
139
|
+
*/
|
|
108
140
|
export async function openGroupDetail(groupId, _groupName) {
|
|
109
141
|
createModal('group-detail-modal', _groupName, `<div class="loading-state"><div class="loading-spinner"></div></div>`, '', true);
|
|
110
142
|
showModal('group-detail-modal');
|
|
@@ -153,6 +185,12 @@ export async function openGroupDetail(groupId, _groupName) {
|
|
|
153
185
|
bodyEl.innerHTML = `<div class="empty-state"><div class="empty-state-text">Error: ${escHtml(err instanceof Error ? err.message : String(err))}</div></div>`;
|
|
154
186
|
}
|
|
155
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Reads the invite field for the given team, requires an email, adds the
|
|
190
|
+
* member through the API, and reopens the team detail on success.
|
|
191
|
+
*
|
|
192
|
+
* @param groupId - identifier of the team to invite into.
|
|
193
|
+
*/
|
|
156
194
|
export async function inviteMember(groupId) {
|
|
157
195
|
const emailEl = document.getElementById(`invite-email-${groupId}`);
|
|
158
196
|
const email = emailEl?.value?.trim() || '';
|
|
@@ -171,6 +209,13 @@ export async function inviteMember(groupId) {
|
|
|
171
209
|
toast(err instanceof Error ? err.message : String(err), 'error');
|
|
172
210
|
}
|
|
173
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Confirms and removes a member from the specified team, then reopens the
|
|
214
|
+
* team detail and surfaces errors as toasts.
|
|
215
|
+
*
|
|
216
|
+
* @param groupId - identifier of the team to modify.
|
|
217
|
+
* @param userId - identifier of the member to remove.
|
|
218
|
+
*/
|
|
174
219
|
export function removeMember(groupId, userId) {
|
|
175
220
|
confirmDialog('Remove Member?', 'Remove this member from the group?', async () => {
|
|
176
221
|
try {
|
|
@@ -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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"}
|
|
@@ -8,6 +8,10 @@ import { showModal, hideModal, createModal, confirmDialog } from '../components/
|
|
|
8
8
|
import { toast } from '../components/toast.js';
|
|
9
9
|
import type { GroupDetailResponse, GroupMemberRow, GroupsResponse, GroupListRow } from '../api-types.js';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Renders the groups page header and a loading placeholder, then fetches and
|
|
13
|
+
* displays the list of teams.
|
|
14
|
+
*/
|
|
11
15
|
export async function renderGroupsView(): Promise<void> {
|
|
12
16
|
const el = document.getElementById('content-groups');
|
|
13
17
|
if (!el) return;
|
|
@@ -23,6 +27,11 @@ export async function renderGroupsView(): Promise<void> {
|
|
|
23
27
|
await loadGroups();
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Fetches the user's teams from the API and renders each as a clickable row
|
|
32
|
+
* showing avatar initials, name, description, and member count, or an empty
|
|
33
|
+
* state when none exist.
|
|
34
|
+
*/
|
|
26
35
|
async function loadGroups(): Promise<void> {
|
|
27
36
|
const el = document.getElementById('groups-list');
|
|
28
37
|
if (!el) return;
|
|
@@ -57,6 +66,10 @@ async function loadGroups(): Promise<void> {
|
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Builds and displays the modal form for creating a new team, with name and
|
|
71
|
+
* description inputs and a validation slot.
|
|
72
|
+
*/
|
|
60
73
|
export function openCreateGroupModal(): void {
|
|
61
74
|
createModal('create-group-modal','New Group',`
|
|
62
75
|
<div class="form-group">
|
|
@@ -74,6 +87,10 @@ export function openCreateGroupModal(): void {
|
|
|
74
87
|
showModal('create-group-modal');
|
|
75
88
|
}
|
|
76
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Reads the create-team form, requires a name, posts the new team, and
|
|
92
|
+
* reloads the list on success, surfacing validation and server errors inline.
|
|
93
|
+
*/
|
|
77
94
|
export async function submitCreateGroup(): Promise<void> {
|
|
78
95
|
const name = (document.getElementById('cg-name') as HTMLInputElement | null)?.value?.trim() || '';
|
|
79
96
|
const description = (document.getElementById('cg-desc') as HTMLInputElement | null)?.value?.trim() || '';
|
|
@@ -90,6 +107,13 @@ export async function submitCreateGroup(): Promise<void> {
|
|
|
90
107
|
}
|
|
91
108
|
}
|
|
92
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Confirms and permanently deletes the named team, then refreshes the list,
|
|
112
|
+
* showing any errors as toasts.
|
|
113
|
+
*
|
|
114
|
+
* @param groupId - identifier of the team to delete.
|
|
115
|
+
* @param name - display name shown in the confirmation prompt.
|
|
116
|
+
*/
|
|
93
117
|
export function deleteGroup(groupId: string, name: string): void {
|
|
94
118
|
confirmDialog('Delete Group?', `Delete group "${name}"? This cannot be undone.`, async () => {
|
|
95
119
|
try {
|
|
@@ -100,6 +124,14 @@ export function deleteGroup(groupId: string, name: string): void {
|
|
|
100
124
|
}, true);
|
|
101
125
|
}
|
|
102
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Opens a modal showing a team's members with an invite-by-email field,
|
|
129
|
+
* fetching the membership from the API and replacing the loading placeholder
|
|
130
|
+
* with the rendered list.
|
|
131
|
+
*
|
|
132
|
+
* @param groupId - identifier of the team to display.
|
|
133
|
+
* @param _groupName - fallback title used before the API response resolves.
|
|
134
|
+
*/
|
|
103
135
|
export async function openGroupDetail(groupId: string, _groupName: string): Promise<void> {
|
|
104
136
|
createModal('group-detail-modal', _groupName,
|
|
105
137
|
`<div class="loading-state"><div class="loading-spinner"></div></div>`, '', true);
|
|
@@ -150,6 +182,12 @@ export async function openGroupDetail(groupId: string, _groupName: string): Prom
|
|
|
150
182
|
}
|
|
151
183
|
}
|
|
152
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Reads the invite field for the given team, requires an email, adds the
|
|
187
|
+
* member through the API, and reopens the team detail on success.
|
|
188
|
+
*
|
|
189
|
+
* @param groupId - identifier of the team to invite into.
|
|
190
|
+
*/
|
|
153
191
|
export async function inviteMember(groupId: string): Promise<void> {
|
|
154
192
|
const emailEl = document.getElementById(`invite-email-${groupId}`) as HTMLInputElement | null;
|
|
155
193
|
const email = emailEl?.value?.trim() || '';
|
|
@@ -162,6 +200,13 @@ export async function inviteMember(groupId: string): Promise<void> {
|
|
|
162
200
|
} catch(err: unknown) { toast(err instanceof Error ? err.message : String(err),'error'); }
|
|
163
201
|
}
|
|
164
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Confirms and removes a member from the specified team, then reopens the
|
|
205
|
+
* team detail and surfaces errors as toasts.
|
|
206
|
+
*
|
|
207
|
+
* @param groupId - identifier of the team to modify.
|
|
208
|
+
* @param userId - identifier of the member to remove.
|
|
209
|
+
*/
|
|
165
210
|
export function removeMember(groupId: string, userId: string): void {
|
|
166
211
|
confirmDialog('Remove Member?', 'Remove this member from the group?', async () => {
|
|
167
212
|
try {
|
|
@@ -18,6 +18,7 @@ const TOPIC_ICONS = {
|
|
|
18
18
|
function topicIcon(id) {
|
|
19
19
|
return TOPIC_ICONS[id] ?? '📖';
|
|
20
20
|
}
|
|
21
|
+
/** Renders the grid of clickable guide topic cards, each showing its icon, title, and summary or intent. */
|
|
21
22
|
function renderTopicCards(topics) {
|
|
22
23
|
if (topics.length === 0) {
|
|
23
24
|
return '<div class="empty-state"><div class="empty-state-text">No guide topics found.</div></div>';
|
|
@@ -31,6 +32,7 @@ function renderTopicCards(topics) {
|
|
|
31
32
|
</div>
|
|
32
33
|
</div>`).join('')}</div>`;
|
|
33
34
|
}
|
|
35
|
+
/** Renders the detail markup for a single guide topic: a back link, title and intent, summary, quick commands with copy buttons, workflows, and related-topic links. */
|
|
34
36
|
function renderTopicDetail(topic) {
|
|
35
37
|
const commands = topic.commands ?? topic.quick_commands ?? [];
|
|
36
38
|
const related = topic.related ?? [];
|
|
@@ -93,6 +95,7 @@ function renderTopicDetail(topic) {
|
|
|
93
95
|
</div>
|
|
94
96
|
</div>` : ''}`;
|
|
95
97
|
}
|
|
98
|
+
/** Renders the guide view for the current project: a list of topic cards when no topic id is given, or a single topic's detail (fetched from the guide topic endpoint) when one is. */
|
|
96
99
|
export async function renderGuideView(topicId) {
|
|
97
100
|
const el = document.getElementById('content-guide');
|
|
98
101
|
if (!el)
|
|
@@ -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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"}
|
|
@@ -21,6 +21,7 @@ function topicIcon(id: string): string {
|
|
|
21
21
|
return TOPIC_ICONS[id] ?? '📖';
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Renders the grid of clickable guide topic cards, each showing its icon, title, and summary or intent. */
|
|
24
25
|
function renderTopicCards(topics: Array<{id: string; title: string; summary?: string; intent?: string}>): string {
|
|
25
26
|
if (topics.length === 0) {
|
|
26
27
|
return '<div class="empty-state"><div class="empty-state-text">No guide topics found.</div></div>';
|
|
@@ -35,6 +36,7 @@ function renderTopicCards(topics: Array<{id: string; title: string; summary?: st
|
|
|
35
36
|
</div>`).join('')}</div>`;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
/** Renders the detail markup for a single guide topic: a back link, title and intent, summary, quick commands with copy buttons, workflows, and related-topic links. */
|
|
38
40
|
function renderTopicDetail(topic: {
|
|
39
41
|
id: string;
|
|
40
42
|
title: string;
|
|
@@ -108,6 +110,7 @@ function renderTopicDetail(topic: {
|
|
|
108
110
|
</div>` : ''}`;
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
/** Renders the guide view for the current project: a list of topic cards when no topic id is given, or a single topic's detail (fetched from the guide topic endpoint) when one is. */
|
|
111
114
|
export async function renderGuideView(topicId?: string): Promise<void> {
|
|
112
115
|
const el = document.getElementById('content-guide');
|
|
113
116
|
if (!el) return;
|
|
@@ -15,6 +15,7 @@ function isHistoryDriftIssue(issue) {
|
|
|
15
15
|
const msg = (issue.message || issue.description || issue.type || '').toLowerCase();
|
|
16
16
|
return msg.includes('history') && (msg.includes('drift') || msg.includes('repair') || msg.includes('mismatch'));
|
|
17
17
|
}
|
|
18
|
+
/** Renders the markup for one health issue, appending Repair and Dry Run buttons when the issue is history drift and a project item id is detected in the message. */
|
|
18
19
|
function renderIssueRow(issue, projectId) {
|
|
19
20
|
const msg = escHtml(issue.message || issue.description || String(issue));
|
|
20
21
|
if (isHistoryDriftIssue(issue)) {
|
|
@@ -28,6 +29,7 @@ function renderIssueRow(issue, projectId) {
|
|
|
28
29
|
}
|
|
29
30
|
return `<div class="health-issue-item">⚠ ${msg}</div>`;
|
|
30
31
|
}
|
|
32
|
+
/** Runs item history repair (or a dry-run preview) via the project's history-repair endpoint and toasts a summary of entries scanned, rehashed, and patched, including verification failures. */
|
|
31
33
|
export async function repairItemHistory(projectId, itemId, dryRun) {
|
|
32
34
|
try {
|
|
33
35
|
const data = await api('POST', `/projects/${projectId}/pm/items/${encodeURIComponent(itemId)}/history-repair`, { dryRun });
|
|
@@ -56,6 +58,7 @@ export async function repairItemHistory(projectId, itemId, dryRun) {
|
|
|
56
58
|
toast(`Repair failed: ${err instanceof Error ? err.message : String(err)}`, 'error');
|
|
57
59
|
}
|
|
58
60
|
}
|
|
61
|
+
/** Renders the project health view: a numeric health score card colored by threshold and a list of issues (each rendered with repair actions when applicable), plus an optional summary. */
|
|
59
62
|
export async function renderHealthView() {
|
|
60
63
|
const el = document.getElementById('content-health');
|
|
61
64
|
if (!el)
|