@transitrix/diagrams 1.12.0 → 1.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/capability-map/CapabilityCardNode.d.ts +1 -0
  2. package/dist/capability-map/CapabilityCardNode.js +5 -4
  3. package/dist/capability-map/CapabilityMapView.js +1 -0
  4. package/dist/capability-map/dsm-layout.js +5 -4
  5. package/dist/capability-map/dsm-schema.d.ts +1 -0
  6. package/dist/edge-path.d.ts +17 -0
  7. package/dist/edge-path.js +33 -4
  8. package/dist/fgca/index.d.ts +1 -1
  9. package/dist/fgca/index.js +1 -1
  10. package/dist/fgca/preview-layout.d.ts +24 -4
  11. package/dist/fgca/preview-layout.js +178 -2
  12. package/dist/goals/GoalNode.d.ts +1 -0
  13. package/dist/goals/GoalNode.js +5 -4
  14. package/dist/goals/GoalTreeView.js +1 -0
  15. package/dist/goals/layout.js +7 -5
  16. package/dist/goals/types.d.ts +2 -0
  17. package/dist/scope.d.ts +27 -0
  18. package/dist/scope.js +40 -0
  19. package/dist/webview/render-fgca.d.ts +3 -0
  20. package/dist/webview/render-fgca.js +5 -3
  21. package/dist/webview/render-goals.d.ts +3 -0
  22. package/dist/webview/render-goals.js +5 -5
  23. package/package.json +1 -1
  24. package/src/capability-map/CapabilityCardNode.tsx +8 -6
  25. package/src/capability-map/CapabilityMapView.tsx +1 -0
  26. package/src/capability-map/dsm-layout.ts +5 -4
  27. package/src/capability-map/dsm-schema.ts +1 -0
  28. package/src/edge-path.ts +45 -4
  29. package/src/fgca/index.ts +2 -0
  30. package/src/fgca/preview-layout.ts +205 -5
  31. package/src/goals/GoalNode.tsx +8 -6
  32. package/src/goals/GoalTreeView.tsx +1 -0
  33. package/src/goals/layout.ts +7 -5
  34. package/src/goals/types.ts +2 -0
  35. package/src/scope.ts +61 -0
  36. package/src/webview/render-fgca.ts +7 -2
  37. package/src/webview/render-goals.ts +7 -5
package/src/scope.ts CHANGED
@@ -20,12 +20,38 @@ export type Scope =
20
20
  | { mode: 'level'; maxLevel: number }
21
21
  | { mode: 'root'; rootGoalId: string };
22
22
 
23
+ /** Column filters on a DGCA/DGA chain. Empty / omitted id = All for that column. */
24
+ export type ChainColumnKey = 'driverId' | 'goalId' | 'changeId' | 'activityId';
25
+
26
+ export type ChainScope = {
27
+ mode: 'chain';
28
+ driverId?: string;
29
+ goalId?: string;
30
+ changeId?: string;
31
+ activityId?: string;
32
+ };
33
+
34
+ /** Scope accepted by DGCA/DGA layout — Goals tree still uses {@link Scope} only. */
35
+ export type FgcaScope = Scope | ChainScope;
36
+
23
37
  /** Default scope — render everything. */
24
38
  export const SCOPE_ALL: Scope = { mode: 'all' };
25
39
 
40
+ export function isChainScope(scope: FgcaScope): scope is ChainScope {
41
+ return scope.mode === 'chain';
42
+ }
43
+
44
+ /** True when at least one column filter is set. */
45
+ export function chainScopeActive(scope: ChainScope): boolean {
46
+ return !!(scope.driverId || scope.goalId || scope.changeId || scope.activityId);
47
+ }
48
+
26
49
  /** Warning code emitted when a root-mode scope names a goal that isn't in the document. */
27
50
  export const SCOPE_MISSING_ROOT_CODE = 'SCOPE-001';
28
51
 
52
+ /** Warning code emitted when a chain-column filter names an id that isn't in the document. */
53
+ export const SCOPE_MISSING_CHAIN_CODE = 'SCOPE-002';
54
+
29
55
  /**
30
56
  * Returns the SCOPE-001 warning when `scope` is root-mode and `rootGoalId` is
31
57
  * not among the document's goal ids; otherwise null. Goal ids are compared as
@@ -44,3 +70,38 @@ export function checkScopeRoot(scope: Scope, goalIds: Iterable<string | number>)
44
70
  message: `Scope root goal "${scope.rootGoalId}" was not found in this document — nothing to show. Clear the scope or pick a goal that exists.`,
45
71
  };
46
72
  }
73
+
74
+ function idSetHas(ids: Iterable<string | number>, want: string): boolean {
75
+ for (const id of ids) {
76
+ if (String(id) === want) return true;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ /**
82
+ * Returns a SCOPE-002 warning when a chain filter names an id that is not in
83
+ * the document's corresponding column; otherwise null. Several missing ids
84
+ * are listed in one message.
85
+ */
86
+ export function checkChainScope(
87
+ scope: ChainScope,
88
+ ids: {
89
+ drivers: Iterable<string | number>;
90
+ goals: Iterable<string | number>;
91
+ changes: Iterable<string | number>;
92
+ activities: Iterable<string | number>;
93
+ },
94
+ hideChanges = false,
95
+ ): ValidationWarning | null {
96
+ if (scope.mode !== 'chain') return null;
97
+ const missing: string[] = [];
98
+ if (scope.driverId && !idSetHas(ids.drivers, scope.driverId)) missing.push(`driver "${scope.driverId}"`);
99
+ if (scope.goalId && !idSetHas(ids.goals, scope.goalId)) missing.push(`goal "${scope.goalId}"`);
100
+ if (!hideChanges && scope.changeId && !idSetHas(ids.changes, scope.changeId)) missing.push(`change "${scope.changeId}"`);
101
+ if (scope.activityId && !idSetHas(ids.activities, scope.activityId)) missing.push(`action "${scope.activityId}"`);
102
+ if (missing.length === 0) return null;
103
+ return {
104
+ code: SCOPE_MISSING_CHAIN_CODE,
105
+ message: `Scope ${missing.join(', ')} was not found in this document — nothing to show for that filter. Clear the scope or pick an id that exists.`,
106
+ };
107
+ }
@@ -21,7 +21,7 @@ import {
21
21
  type FGCAPreviewColumn,
22
22
  } from '../fgca/preview-layout.js';
23
23
  import type { FGCADoc } from '../fgca/validate.js';
24
- import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
24
+ import { previewEdgePath, DEFAULT_EDGE_CURVATURE, type EdgeStyle } from '../edge-path.js';
25
25
  import { parseNodeSizePreset, resolveDgcaNodeSize, type NodeSizePreset } from '../node-size-presets.js';
26
26
  import { generateSvgEmbedCss } from '../theme/index.js';
27
27
  import { emitCenteredTextSvg, layoutCenteredEntityText, truncateLine } from './entity-text-layout.js';
@@ -39,6 +39,7 @@ type FgcaLayout = ReturnType<typeof layoutFGCAPreview>;
39
39
  export interface RenderFgcaBodyOptions {
40
40
  nodeWidth?: number;
41
41
  nodeHeight?: number;
42
+ edgeStyle?: EdgeStyle;
42
43
  }
43
44
 
44
45
  /**
@@ -60,6 +61,7 @@ export function renderFgcaBody(
60
61
  ): string {
61
62
  const nodeWidth = bodyOptions.nodeWidth ?? FGCA_NODE_W;
62
63
  const nodeHeight = bodyOptions.nodeHeight ?? FGCA_NODE_H;
64
+ const edgeStyle = bodyOptions.edgeStyle;
63
65
  const headerTruncate = Math.max(8, Math.floor((nodeWidth - 16) / 7));
64
66
 
65
67
  const headerSvg = columns
@@ -71,7 +73,7 @@ export function renderFgcaBody(
71
73
  const edgeSvg = edges
72
74
  .map(
73
75
  (e) =>
74
- `<path d="${horizontalCubicEdgePath(e.sx, e.sy, e.tx, e.ty, curvature, entryCurvature)}" class="diagram-edge" marker-end="url(#arrow)"/>`,
76
+ `<path d="${previewEdgePath(e.sx, e.sy, e.tx, e.ty, edgeStyle, curvature, entryCurvature)}" class="diagram-edge" marker-end="url(#arrow)"/>`,
75
77
  )
76
78
  .join('\n');
77
79
 
@@ -108,6 +110,7 @@ export interface RenderFgcaOptions {
108
110
  curvature?: number;
109
111
  /** Entry curvature at the target node; defaults to `curvature` when omitted. */
110
112
  entryCurvature?: number;
113
+ edgeStyle?: EdgeStyle;
111
114
  nodeSizePreset?: NodeSizePreset;
112
115
  layoutOptions?: Parameters<typeof layoutFGCAPreview>[1];
113
116
  /** Show scope caption when goal-scoped projection omits goals that reference out-of-scope goals. */
@@ -120,6 +123,7 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
120
123
  title = '',
121
124
  curvature = DEFAULT_EDGE_CURVATURE,
122
125
  entryCurvature,
126
+ edgeStyle,
123
127
  nodeSizePreset = 'normal',
124
128
  layoutOptions,
125
129
  scopeCaption = false,
@@ -141,6 +145,7 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
141
145
  const body = renderFgcaBody(columns, nodes, edges, curvature, entryCurvature, {
142
146
  nodeWidth: nodeSize.width,
143
147
  nodeHeight: nodeSize.height,
148
+ edgeStyle,
144
149
  });
145
150
 
146
151
  const titleSvg = title
@@ -13,7 +13,7 @@
13
13
  import { layoutGoalTree } from '../goals/layout.js';
14
14
  import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
15
15
  import { displayGoalId } from '../goals/parse-canonical.js';
16
- import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
16
+ import { previewEdgePath, DEFAULT_EDGE_CURVATURE, type EdgeStyle } from '../edge-path.js';
17
17
  import { parseNodeSizePreset, resolveGoalsNodeSize, type NodeSizePreset } from '../node-size-presets.js';
18
18
  import { generateSvgEmbedCss, type ThemeId } from '../theme/index.js';
19
19
  import { emitCenteredTextSvg, layoutCenteredEntityText } from './entity-text-layout.js';
@@ -28,6 +28,7 @@ export interface RenderGoalsOptions {
28
28
  treeName?: string;
29
29
  curvature?: number;
30
30
  entryCurvature?: number;
31
+ edgeStyle?: EdgeStyle;
31
32
  nodeSizePreset?: NodeSizePreset;
32
33
  }
33
34
 
@@ -37,7 +38,7 @@ export interface RenderGoalsOptions {
37
38
  * with the shared theme CSS embedded so the output is self-contained.
38
39
  */
39
40
  export function renderGoalsSvg(tree: GoalTree, options: RenderGoalsOptions = {}): string {
40
- const { treeName = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, nodeSizePreset = 'normal' } = options;
41
+ const { treeName = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, edgeStyle, nodeSizePreset = 'normal' } = options;
41
42
  const nodeSize = resolveGoalsNodeSize(parseNodeSizePreset(nodeSizePreset));
42
43
 
43
44
  const layout: GoalTreeLayout = layoutGoalTree(tree, {
@@ -55,12 +56,13 @@ export function renderGoalsSvg(tree: GoalTree, options: RenderGoalsOptions = {})
55
56
  ? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(`Goal tree — ${treeName}`)}</text>`
56
57
  : '';
57
58
 
58
- return renderGoalsLayoutSvg(layout, { curvature, entryCurvature, title, embedCssTheme: 'transitrix' });
59
+ return renderGoalsLayoutSvg(layout, { curvature, entryCurvature, edgeStyle, title, embedCssTheme: 'transitrix' });
59
60
  }
60
61
 
61
62
  export interface RenderGoalsLayoutOptions {
62
63
  curvature?: number;
63
64
  entryCurvature?: number;
65
+ edgeStyle?: EdgeStyle;
64
66
  /** Extra vertical space reserved at the top of the canvas (e.g. for a title block). */
65
67
  topInset?: number;
66
68
  /** Raw SVG injected immediately after `<defs>` — a header line or a full title block. */
@@ -78,7 +80,7 @@ export interface RenderGoalsLayoutOptions {
78
80
  * and the export path own styling).
79
81
  */
80
82
  export function renderGoalsLayoutSvg(layout: GoalTreeLayout, options: RenderGoalsLayoutOptions = {}): string {
81
- const { curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, topInset = 0, title = '', embedCssTheme } = options;
83
+ const { curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, edgeStyle, topInset = 0, title = '', embedCssTheme } = options;
82
84
 
83
85
  const w = layout.bounds.width + PAD * 2;
84
86
  const h = layout.bounds.height + PAD * 2 + topInset;
@@ -95,7 +97,7 @@ export function renderGoalsLayoutSvg(layout: GoalTreeLayout, options: RenderGoal
95
97
  const sy = s.y + oy + s.height / 2;
96
98
  const tx = t.x + ox;
97
99
  const ty = t.y + oy + t.height / 2;
98
- return horizontalCubicEdgePath(sx, sy, tx, ty, curvature, entryCurvature);
100
+ return previewEdgePath(sx, sy, tx, ty, edgeStyle, curvature, entryCurvature);
99
101
  }
100
102
 
101
103
  const edgeSvg = layout.edges