@transitrix/diagrams 1.13.2 → 1.14.0-rc.0

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.
@@ -176,6 +176,8 @@ export interface ObjectDetailInput {
176
176
  }
177
177
 
178
178
  export interface ComplianceIndexInput {
179
+ records?: ChainRecord[];
180
+ findings?: ChainFinding[];
179
181
  requirements: IndexRequirement[];
180
182
  assertions: IndexAssertion[];
181
183
  /** Optional — absent inputs (existing callers) simply index no verifications. */
@@ -188,6 +190,8 @@ export interface ComplianceIndexInput {
188
190
 
189
191
  /** The reverse-index — all maps are keyed by canonical id. */
190
192
  export interface ComplianceIndex {
193
+ recordsById: Map<string, ChainRecord[]>;
194
+ findings: ChainFinding[];
191
195
  requirementById: Map<string, IndexRequirement>;
192
196
  /** Codex artefact id → requirements whose `derived_from` names it. */
193
197
  requirementsByLaw: Map<string, IndexRequirement[]>;
@@ -313,3 +317,20 @@ export interface TraceElementCatalog {
313
317
  /** id → human-readable name (or the id itself when unnamed). */
314
318
  nameById: Map<string, string>;
315
319
  }
320
+
321
+ /** Lossless records retained beside the compatible legacy projections. */
322
+ export interface ChainRecord {
323
+ id: string;
324
+ type: string;
325
+ sourcePath: string;
326
+ raw: Record<string, unknown>;
327
+ }
328
+ export interface ChainFinding {
329
+ id: string;
330
+ owner: string;
331
+ field: string;
332
+ code: string;
333
+ message: string;
334
+ reference: boolean;
335
+ severity: 'error' | 'warning';
336
+ }
@@ -332,6 +332,7 @@ export function parseCanonicalFGCA(
332
332
  name: String(el!['name'] ?? ''),
333
333
  goal_id: goalRefs.length > 0 ? goalRefs[0] : null,
334
334
  ...(typeVal !== undefined ? { type: typeVal } : {}),
335
+ ...(typeof el!['link'] === 'string' ? { link: el!['link'] } : {}),
335
336
  ...(progressVal !== undefined ? { progress: progressVal } : {}),
336
337
  };
337
338
  });
@@ -30,6 +30,7 @@ export interface FGCAPreviewChange {
30
30
  activity_ids: Array<number | string>;
31
31
  }
32
32
  export interface FGCAPreviewActivity {
33
+ link?: string;
33
34
  id: number | string;
34
35
  name: string;
35
36
  goal_id?: number | string | null;
@@ -387,7 +388,7 @@ export function layoutFGCAPreview(
387
388
  ...changes.map(c => ({ id: `change_${c.id}`, label: c.name })),
388
389
  ...virtualChanges.map(v => ({ id: v.id, label: '–', virtual: true })),
389
390
  ],
390
- activity: doc.activities.map(a => ({ id: `activity_${a.id}`, label: a.name, progress: a.progress, linked: a.goal_id != null })),
391
+ activity: doc.activities.map(a => ({ id: `activity_${a.id}`, label: a.name, progress: a.progress, linked: Boolean(a.link?.trim()) })),
391
392
  };
392
393
 
393
394
  // Build predecessor map: for each node, which node IDs in the previous column
package/src/fgca/types.ts CHANGED
@@ -25,6 +25,8 @@ export interface ActivityItem {
25
25
  activity_type_id?: number;
26
26
  /** Canonical values: initiative | programme | project | work_package. Unknown values pass through without error. */
27
27
  type?: string;
28
+ /** Tracker link, independent of goal relationships. */
29
+ link?: string;
28
30
  progress?: {
29
31
  percent: number;
30
32
  computedAt: string;
@@ -0,0 +1,18 @@
1
+ /** Shared completion display contract for Action, DGA and DGCA previews. */
2
+ export interface ActionProgress {
3
+ percent: number;
4
+ computedAt: string;
5
+ }
6
+
7
+ export function completionPercentState(progress: ActionProgress | undefined, linked: boolean, enabled = true, now = Date.now()): { label: string; stale: boolean } | undefined {
8
+ if (!enabled) return undefined;
9
+ if (progress && Number.isFinite(progress.percent) && progress.percent >= 0 && progress.percent <= 100 && Number.isFinite(Date.parse(progress.computedAt))) {
10
+ return { label: `${progress.percent}%`, stale: now - Date.parse(progress.computedAt) > 7 * 24 * 60 * 60 * 1000 };
11
+ }
12
+ return linked ? { label: '–%', stale: false } : undefined;
13
+ }
14
+
15
+ export function completionPercentSvg(progress: ActionProgress | undefined, linked: boolean, x: number, y: number, enabled = true): string {
16
+ const state = completionPercentState(progress, linked, enabled);
17
+ return state ? `<text class="text-id completion-percent" x="${x}" y="${y}" text-anchor="end" dominant-baseline="hanging" font-size="10"${state.stale ? ' opacity="0.5"' : ''}>${state.label}</text>` : '';
18
+ }
@@ -16,6 +16,7 @@
16
16
  * in `renderActivitiesNetworkBody` and is shared verbatim with the VS Code
17
17
  * preview's `networkSvg`.
18
18
  */
19
+ import { completionPercentSvg } from './completion-percent.js';
19
20
  import { layoutActivities } from '../activities/layout.js';
20
21
  import { computeCpm } from '../activities/cpm.js';
21
22
  import type {
@@ -147,6 +148,7 @@ function activityNodeSvg(
147
148
  return [
148
149
  `<rect class="${cls}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="8"/>`,
149
150
  textSvg,
151
+ completionPercentSvg(n.data.progress, Boolean(n.data.link?.trim()), x + n.width - 8, y + 6),
150
152
  durLabel ? `<text class="text-secondary" x="${x + n.width - 8}" y="${y + n.height - 8}" text-anchor="end">${durLabel}</text>` : '',
151
153
  ].filter(Boolean).join('\n');
152
154
  }
@@ -12,6 +12,7 @@
12
12
  * `change` column (`hideChanges` / the layout's Goal → Activity collapse),
13
13
  * exactly as the extension preview does.
14
14
  */
15
+ import { completionPercentSvg } from './completion-percent.js';
15
16
  import {
16
17
  layoutFGCAPreview,
17
18
  FGCA_NODE_W,
@@ -65,7 +66,6 @@ export function renderFgcaBody(
65
66
  const edgeStyle = bodyOptions.edgeStyle;
66
67
  const showCompletionPercent = bodyOptions.showCompletionPercent ?? true;
67
68
  const headerTruncate = Math.max(8, Math.floor((nodeWidth - 16) / 7));
68
- const STALENESS_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;
69
69
 
70
70
  const headerSvg = columns
71
71
  .map(({ col, x }) =>
@@ -103,20 +103,8 @@ export function renderFgcaBody(
103
103
  const typeBadge = n.type && n.col === 'activity'
104
104
  ? `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 10}" text-anchor="end" dominant-baseline="hanging" font-size="10">${escXml(n.type)}</text>`
105
105
  : '';
106
- const progressBadge = showCompletionPercent && n.col === 'activity'
107
- ? (() => {
108
- if (n.progress) {
109
- const computedAt = new Date(n.progress.computedAt);
110
- const now = new Date();
111
- const isStale = now.getTime() - computedAt.getTime() > STALENESS_THRESHOLD_MS;
112
- const opacity = isStale ? ' opacity="0.5"' : '';
113
- return `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 22}" text-anchor="end" dominant-baseline="hanging" font-size="10"${opacity}>${escXml(n.progress.percent + '%')}</text>`;
114
- }
115
- if (n.linked) {
116
- return `<text class="text-id" x="${n.x + nodeWidth - 6}" y="${n.y + 22}" text-anchor="end" dominant-baseline="hanging" font-size="10">–%</text>`;
117
- }
118
- return '';
119
- })()
106
+ const progressBadge = n.col === 'activity'
107
+ ? completionPercentSvg(n.progress, Boolean(n.linked), n.x + nodeWidth - 6, n.y + 22, showCompletionPercent)
120
108
  : '';
121
109
  return [
122
110
  `<g>`,