@stephen-netu/design-system 0.1.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.
Files changed (204) hide show
  1. package/README.md +66 -0
  2. package/package.json +130 -0
  3. package/src/b0-app-shell/clarification-overlay/ClarificationOverlay.svelte +221 -0
  4. package/src/b0-app-shell/clarification-overlay/index.ts +1 -0
  5. package/src/b0-app-shell/command-bar/CommandBar.svelte +565 -0
  6. package/src/b0-app-shell/index.ts +14 -0
  7. package/src/base.css +152 -0
  8. package/src/d0-data-viz/BarChart.svelte +236 -0
  9. package/src/d0-data-viz/CheckpointBar.svelte +574 -0
  10. package/src/d0-data-viz/CheckpointPill.svelte +297 -0
  11. package/src/d0-data-viz/MetricCard.svelte +159 -0
  12. package/src/d0-data-viz/SparkLine.svelte +66 -0
  13. package/src/d0-data-viz/StorageMap.svelte +182 -0
  14. package/src/d0-data-viz/Timeline.svelte +188 -0
  15. package/src/d0-data-viz/checkpoint-types.ts +162 -0
  16. package/src/d0-data-viz/d0-tokens.css +32 -0
  17. package/src/d0-data-viz/index.ts +58 -0
  18. package/src/d0-data-viz/storage-map-types.ts +142 -0
  19. package/src/d0-data-viz/theme-integration.ts +134 -0
  20. package/src/d0-data-viz/types.ts +53 -0
  21. package/src/demos/BspSizingInfographic.svelte +386 -0
  22. package/src/demos/BspZoneDemo.svelte +255 -0
  23. package/src/demos/ControlRoomDemo.svelte +570 -0
  24. package/src/demos/NodepadBlocksShowcase.svelte +189 -0
  25. package/src/demos/index.ts +7 -0
  26. package/src/editor/CodeEditor.svelte +188 -0
  27. package/src/editor/FindReplaceDialog.svelte +516 -0
  28. package/src/editor/MarkdownEditor.svelte +660 -0
  29. package/src/editor/index.ts +2 -0
  30. package/src/entity-graph-view/WorldView.svelte +171 -0
  31. package/src/entity-graph-view/index.ts +6 -0
  32. package/src/entity-graph-view/types.ts +364 -0
  33. package/src/epistemic/index.ts +2 -0
  34. package/src/epistemic/types.ts +134 -0
  35. package/src/epistemic/utils.ts +32 -0
  36. package/src/f0-forms/checkbox/Checkbox.svelte +158 -0
  37. package/src/f0-forms/field/FormField.svelte +91 -0
  38. package/src/f0-forms/index.ts +11 -0
  39. package/src/f0-forms/radio/RadioGroup.svelte +192 -0
  40. package/src/f0-forms/search/SearchBar.svelte +99 -0
  41. package/src/f0-forms/select/Select.svelte +198 -0
  42. package/src/f0-forms/textarea/TextArea.svelte +170 -0
  43. package/src/f0-forms/toggle/FormToggle.svelte +147 -0
  44. package/src/f0-forms/validation/validate.ts +47 -0
  45. package/src/fabric/ai/ChatPanel.svelte +648 -0
  46. package/src/fabric/ai/index.ts +11 -0
  47. package/src/fabric/data/index.ts +21 -0
  48. package/src/fabric/feedback/StatusBar.svelte +274 -0
  49. package/src/fabric/feedback/ToastContainer.svelte +122 -0
  50. package/src/fabric/feedback/index.ts +14 -0
  51. package/src/fabric/index.ts +37 -0
  52. package/src/fabric/input/CommandBar.svelte +79 -0
  53. package/src/fabric/input/KeyboardShortcuts.svelte +388 -0
  54. package/src/fabric/input/index.ts +12 -0
  55. package/src/fabric/layout/GlassBox.svelte +193 -0
  56. package/src/fabric/layout/KanbanBoard.svelte +158 -0
  57. package/src/fabric/layout/KanbanCard.svelte +226 -0
  58. package/src/fabric/layout/KanbanColumn.svelte +278 -0
  59. package/src/fabric/layout/index.ts +17 -0
  60. package/src/fabric/navigation/ActivityRail.svelte +271 -0
  61. package/src/fabric/navigation/index.ts +11 -0
  62. package/src/g0-geo/SovereignMap.svelte +149 -0
  63. package/src/g0-geo/index.ts +2 -0
  64. package/src/index.ts +68 -0
  65. package/src/l0-layout/block-writer/BlockConnectionLines.svelte +98 -0
  66. package/src/l0-layout/block-writer/BlockContent.svelte +213 -0
  67. package/src/l0-layout/block-writer/BlockHeader.svelte +230 -0
  68. package/src/l0-layout/block-writer/BlockItem.svelte +177 -0
  69. package/src/l0-layout/block-writer/BlockWriter.svelte +219 -0
  70. package/src/l0-layout/block-writer/block-writer-types.ts +63 -0
  71. package/src/l0-layout/block-writer/index.ts +24 -0
  72. package/src/l0-layout/block-writer/markdown.ts +110 -0
  73. package/src/l0-layout/chat-view/ChatViewLayout.svelte +61 -0
  74. package/src/l0-layout/epistemic/AllayDetailPanel.svelte +113 -0
  75. package/src/l0-layout/epistemic/AssayDetailPanel.svelte +164 -0
  76. package/src/l0-layout/epistemic/AvenueTrace.svelte +253 -0
  77. package/src/l0-layout/epistemic/KnowledgeDetailPanel.svelte +204 -0
  78. package/src/l0-layout/epistemic/ParadigmLens.svelte +170 -0
  79. package/src/l0-layout/epistemic/ProvenanceChain.svelte +164 -0
  80. package/src/l0-layout/epistemic/TrustView.svelte +195 -0
  81. package/src/l0-layout/epistemic/index.ts +7 -0
  82. package/src/l0-layout/grove/AccordCard.svelte +156 -0
  83. package/src/l0-layout/grove/GroveCard.svelte +197 -0
  84. package/src/l0-layout/grove/MemberBadge.svelte +96 -0
  85. package/src/l0-layout/grove/MemberList.svelte +59 -0
  86. package/src/l0-layout/grove/grove-types.ts +29 -0
  87. package/src/l0-layout/grove/index.ts +14 -0
  88. package/src/l0-layout/index.ts +45 -0
  89. package/src/l0-layout/kanban/KanbanBoard.svelte +71 -0
  90. package/src/l0-layout/kanban/KanbanCard.svelte +276 -0
  91. package/src/l0-layout/kanban/KanbanColumn.svelte +198 -0
  92. package/src/l0-layout/kanban/KanbanViewLayout.svelte +61 -0
  93. package/src/l0-layout/workspace/WorkspaceLayout.svelte +93 -0
  94. package/src/n0-node-graph/GraphEditor.svelte +342 -0
  95. package/src/n0-node-graph/GraphHistory.svelte +122 -0
  96. package/src/n0-node-graph/Node.svelte +269 -0
  97. package/src/n0-node-graph/NodeCanvas.svelte +187 -0
  98. package/src/n0-node-graph/NodeEdge.svelte +152 -0
  99. package/src/n0-node-graph/NodePalette.svelte +318 -0
  100. package/src/n0-node-graph/NodePort.svelte +128 -0
  101. package/src/n0-node-graph/index.ts +33 -0
  102. package/src/n0-node-graph/n0-tokens.css +29 -0
  103. package/src/n0-node-graph/node-port-types.ts +19 -0
  104. package/src/n0-node-graph/types.ts +96 -0
  105. package/src/n0-node-graph/validate.test.ts +722 -0
  106. package/src/n0-node-graph/validate.ts +371 -0
  107. package/src/n1-flow-canvas/FlowCanvas.svelte +202 -0
  108. package/src/n1-flow-canvas/graph-state.svelte.ts +95 -0
  109. package/src/n1-flow-canvas/index.ts +21 -0
  110. package/src/n1-flow-canvas/layout.ts +118 -0
  111. package/src/n1-flow-canvas/layout.worker.ts +128 -0
  112. package/src/n1-flow-canvas/types.ts +102 -0
  113. package/src/n1-flow-canvas/utils.ts +146 -0
  114. package/src/n2-manifold-disk/ManifoldDisk.svelte +314 -0
  115. package/src/n2-manifold-disk/index.ts +6 -0
  116. package/src/n2-manifold-disk/poincare.ts +184 -0
  117. package/src/n3-force-graph/ForceGraph.svelte +180 -0
  118. package/src/n3-force-graph/GraphTooltip.svelte +99 -0
  119. package/src/n3-force-graph/animated-edge.ts +107 -0
  120. package/src/n3-force-graph/health-node.ts +128 -0
  121. package/src/n3-force-graph/index.ts +31 -0
  122. package/src/n3-force-graph/types.ts +94 -0
  123. package/src/n4-adaptive-manifold/AdaptiveManifold.svelte +1165 -0
  124. package/src/n4-adaptive-manifold/DetailCard.svelte +212 -0
  125. package/src/n4-adaptive-manifold/SuggestionIndicator.svelte +91 -0
  126. package/src/n4-adaptive-manifold/classifier.ts +139 -0
  127. package/src/n4-adaptive-manifold/index.ts +45 -0
  128. package/src/n4-adaptive-manifold/n4-tokens.css +155 -0
  129. package/src/n4-adaptive-manifold/orchestrator.ts +301 -0
  130. package/src/n4-adaptive-manifold/phase-theme.ts +92 -0
  131. package/src/n4-adaptive-manifold/pinnacle-events.ts +135 -0
  132. package/src/n4-adaptive-manifold/providers/gravitational-provider.ts +191 -0
  133. package/src/n4-adaptive-manifold/providers/manifold-provider.ts +276 -0
  134. package/src/n4-adaptive-manifold/transition.ts +90 -0
  135. package/src/n4-adaptive-manifold/types.ts +313 -0
  136. package/src/p0-primitives/index.ts +6 -0
  137. package/src/p0-primitives/tokens/control-room.css +309 -0
  138. package/src/p0-primitives/tokens/index.ts +6 -0
  139. package/src/p0-primitives/tokens/tokens.css +140 -0
  140. package/src/p0-primitives/tokens/tokens.json +132 -0
  141. package/src/p0-primitives/types/index.ts +123 -0
  142. package/src/p0-primitives/types/plugin-types.ts +74 -0
  143. package/src/p0-primitives/utils/index.ts +39 -0
  144. package/src/platform/hud-protocol.ts +20 -0
  145. package/src/platform/index.ts +3 -0
  146. package/src/s0-state/create-store.ts +84 -0
  147. package/src/s0-state/derived.ts +41 -0
  148. package/src/s0-state/index.ts +6 -0
  149. package/src/showcase/Showcase.svelte +401 -0
  150. package/src/spatial/BspTilingCanvas.svelte +232 -0
  151. package/src/spatial/ForceCanvas.svelte +289 -0
  152. package/src/spatial/LodRenderer.svelte +334 -0
  153. package/src/spatial/SnapZoomCamera.svelte +185 -0
  154. package/src/spatial/ZoneTiler.svelte +311 -0
  155. package/src/spatial/camera-types.ts +45 -0
  156. package/src/spatial/content-proportional-bsp.ts +253 -0
  157. package/src/spatial/force-canvas-types.ts +102 -0
  158. package/src/spatial/index.ts +15 -0
  159. package/src/spatial/lod-types.ts +70 -0
  160. package/src/spatial/zone-tiler-types.ts +177 -0
  161. package/src/stores/vaultStore.svelte +14 -0
  162. package/src/t0-transport/connection.ts +34 -0
  163. package/src/t0-transport/error.ts +37 -0
  164. package/src/t0-transport/events.ts +50 -0
  165. package/src/t0-transport/index.ts +6 -0
  166. package/src/t0-transport/invoke.ts +30 -0
  167. package/src/tauri/index.ts +13 -0
  168. package/src/u0-primitives/allay-gauge/AllayGauge.svelte +90 -0
  169. package/src/u0-primitives/allay-gauge/index.ts +1 -0
  170. package/src/u0-primitives/animated-icon/AnimatedIcon.svelte +273 -0
  171. package/src/u0-primitives/animated-icon/README.md +53 -0
  172. package/src/u0-primitives/animated-icon/index.ts +31 -0
  173. package/src/u0-primitives/assay-badge/AssayBadge.svelte +121 -0
  174. package/src/u0-primitives/assay-badge/AssaySignalBar.svelte +83 -0
  175. package/src/u0-primitives/assay-badge/index.ts +2 -0
  176. package/src/u0-primitives/avatar/Avatar.svelte +177 -0
  177. package/src/u0-primitives/badge/Badge.svelte +149 -0
  178. package/src/u0-primitives/button/Button.svelte +304 -0
  179. package/src/u0-primitives/card/Card.svelte +182 -0
  180. package/src/u0-primitives/dropdown/Dropdown.svelte +305 -0
  181. package/src/u0-primitives/expert-badge/ExpertBadge.svelte +89 -0
  182. package/src/u0-primitives/expert-badge/index.ts +1 -0
  183. package/src/u0-primitives/index.ts +35 -0
  184. package/src/u0-primitives/input/Input.svelte +237 -0
  185. package/src/u0-primitives/modal/Modal.svelte +331 -0
  186. package/src/u0-primitives/modal/index.ts +1 -0
  187. package/src/u0-primitives/spinner/Spinner.svelte +129 -0
  188. package/src/u0-primitives/tabs/Tabs.svelte +225 -0
  189. package/src/u0-primitives/tabs/tabs-types.ts +11 -0
  190. package/src/u0-primitives/toggle/Toggle.svelte +161 -0
  191. package/src/u0-primitives/tooltip/Tooltip.svelte +234 -0
  192. package/src/v0-diagrams/DiagramPanel.svelte +68 -0
  193. package/src/v0-diagrams/MarkmapDiagram.svelte +56 -0
  194. package/src/v0-diagrams/MermaidDiagram.svelte +53 -0
  195. package/src/v0-diagrams/index.ts +4 -0
  196. package/src/v0-diagrams/types.ts +28 -0
  197. package/src/v0-diagrams/v0-tokens.css +6 -0
  198. package/src/vite-env.d.ts +7 -0
  199. package/src/x0-enchanted-blocks/ENCHANTED_BLOCKS_SPEC.md +457 -0
  200. package/src/x0-enchanted-blocks/components/EnchantedBlock.svelte +485 -0
  201. package/src/x0-enchanted-blocks/components/NodepadBlock.svelte +262 -0
  202. package/src/x0-enchanted-blocks/core/block-layout.ts +211 -0
  203. package/src/x0-enchanted-blocks/core/block-types.ts +227 -0
  204. package/src/x0-enchanted-blocks/index.ts +46 -0
@@ -0,0 +1,118 @@
1
+ // N1 layout — ELK-based graph layout + BFS order
2
+ // Runs on the main thread (ELK is async/Promise-based; won't block UI).
3
+ // Extracted from layout.worker.ts so FlowCanvas can call it directly,
4
+ // avoiding nested Worker creation which fails in Tauri's WKWebView.
5
+
6
+ import ELK from 'elkjs/lib/elk.bundled.js';
7
+ import type { LayoutResult, NodeLayout } from './types.js';
8
+ import type { GraphSnapshot, GraphNode, GraphEdge } from './types.js';
9
+
10
+ const elk = new ELK();
11
+
12
+ // ── BFS ───────────────────────────────────────────────────────────────────────
13
+
14
+ function computeBfsOrder(
15
+ nodes: ReadonlyArray<GraphNode>,
16
+ edges: ReadonlyArray<GraphEdge>,
17
+ startId: string | null,
18
+ ): string[] {
19
+ const adj = new Map<string, string[]>();
20
+ for (const n of nodes) adj.set(n.id, []);
21
+ for (const e of edges) {
22
+ const neighbors = adj.get(e.sourceId);
23
+ if (neighbors) neighbors.push(e.targetId);
24
+ }
25
+
26
+ const rootId = startId ?? nodes[0]?.id;
27
+ if (!rootId) return [];
28
+
29
+ const visited = new Set<string>();
30
+ const queue: string[] = [rootId];
31
+ const order: string[] = [];
32
+ visited.add(rootId);
33
+
34
+ while (queue.length > 0) {
35
+ const current = queue.shift()!;
36
+ order.push(current);
37
+ const neighbors = (adj.get(current) ?? []).slice().sort(); // S-02: deterministic
38
+ for (const neighbor of neighbors) {
39
+ if (!visited.has(neighbor)) {
40
+ visited.add(neighbor);
41
+ queue.push(neighbor);
42
+ }
43
+ }
44
+ }
45
+
46
+ for (const n of nodes) {
47
+ if (!visited.has(n.id)) order.push(n.id);
48
+ }
49
+
50
+ return order;
51
+ }
52
+
53
+ // ── ELK layout ────────────────────────────────────────────────────────────────
54
+
55
+ const NODE_WIDTH = 120;
56
+ const NODE_HEIGHT = 60;
57
+
58
+ export async function runLayout(
59
+ snapshot: GraphSnapshot,
60
+ _canvasWidth: number,
61
+ _canvasHeight: number,
62
+ ): Promise<LayoutResult> {
63
+ const elkGraph = {
64
+ id: 'root',
65
+ layoutOptions: {
66
+ 'elk.algorithm': 'layered',
67
+ 'elk.direction': 'RIGHT',
68
+ 'elk.spacing.nodeNode': '30',
69
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '80',
70
+ 'elk.padding': '[top=40,left=40,bottom=40,right=40]',
71
+ 'elk.layered.nodePlacement.strategy': 'BRANDES_KOEPF',
72
+ },
73
+ children: snapshot.nodes.map(n => ({
74
+ id: n.id,
75
+ width: NODE_WIDTH,
76
+ height: NODE_HEIGHT,
77
+ })),
78
+ edges: snapshot.edges.map(e => ({
79
+ id: e.id,
80
+ sources: [e.sourceId],
81
+ targets: [e.targetId],
82
+ })),
83
+ };
84
+
85
+ const result = await elk.layout(elkGraph);
86
+
87
+ const positions = new Map<string, NodeLayout>();
88
+ for (const child of result.children ?? []) {
89
+ if (child.x !== undefined && child.y !== undefined) {
90
+ positions.set(child.id, {
91
+ id: child.id,
92
+ x: child.x,
93
+ y: child.y,
94
+ width: child.width ?? NODE_WIDTH,
95
+ height: child.height ?? NODE_HEIGHT,
96
+ });
97
+ }
98
+ }
99
+
100
+ // Grid fallback for nodes ELK dropped (should not happen)
101
+ let col = 0;
102
+ for (const n of snapshot.nodes) {
103
+ if (!positions.has(n.id)) {
104
+ positions.set(n.id, {
105
+ id: n.id,
106
+ x: 40 + col * (NODE_WIDTH + 40),
107
+ y: 40,
108
+ width: NODE_WIDTH,
109
+ height: NODE_HEIGHT,
110
+ });
111
+ col++;
112
+ }
113
+ }
114
+
115
+ const bfsOrder = computeBfsOrder(snapshot.nodes, snapshot.edges, snapshot.focusNodeId);
116
+
117
+ return { positions, bfsOrder };
118
+ }
@@ -0,0 +1,128 @@
1
+ // N1 Layout Worker — ELK.js layout + BFS traversal order
2
+ // Runs in a dedicated Web Worker (mandatory at mount, never on main thread)
3
+ // S-02: BFS neighbors sorted before enqueue for deterministic order
4
+
5
+ import ELK from 'elkjs/lib/elk.bundled.js';
6
+ import type { WorkerIn, WorkerOut, LayoutResult, NodeLayout } from './types.js';
7
+ import type { GraphSnapshot, GraphNode, GraphEdge } from './types.js';
8
+
9
+ const elk = new ELK();
10
+
11
+ // ── BFS ───────────────────────────────────────────────────────────────────────
12
+
13
+ function computeBfsOrder(
14
+ nodes: ReadonlyArray<GraphNode>,
15
+ edges: ReadonlyArray<GraphEdge>,
16
+ startId: string | null,
17
+ ): string[] {
18
+ // Build adjacency with Map (S-02: no object spread iteration)
19
+ const adj = new Map<string, string[]>();
20
+ for (const n of nodes) adj.set(n.id, []);
21
+ for (const e of edges) {
22
+ const neighbors = adj.get(e.sourceId);
23
+ if (neighbors) neighbors.push(e.targetId);
24
+ }
25
+
26
+ // Root: prefer explicit startId, fall back to first node in declaration order
27
+ const rootId = startId ?? nodes[0]?.id;
28
+ if (!rootId) return [];
29
+
30
+ const visited = new Set<string>();
31
+ const queue: string[] = [rootId];
32
+ const order: string[] = [];
33
+ visited.add(rootId);
34
+
35
+ while (queue.length > 0) {
36
+ const current = queue.shift()!;
37
+ order.push(current);
38
+ // Sort neighbors for determinism (S-02)
39
+ const neighbors = (adj.get(current) ?? []).slice().sort();
40
+ for (const neighbor of neighbors) {
41
+ if (!visited.has(neighbor)) {
42
+ visited.add(neighbor);
43
+ queue.push(neighbor);
44
+ }
45
+ }
46
+ }
47
+
48
+ // Append any unreachable nodes in declaration order (disconnected subgraphs)
49
+ for (const n of nodes) {
50
+ if (!visited.has(n.id)) order.push(n.id);
51
+ }
52
+
53
+ return order;
54
+ }
55
+
56
+ // ── ELK layout ────────────────────────────────────────────────────────────────
57
+
58
+ const NODE_WIDTH = 120;
59
+ const NODE_HEIGHT = 60;
60
+
61
+ async function runLayout(snapshot: GraphSnapshot, canvasWidth: number, canvasHeight: number): Promise<LayoutResult> {
62
+ const elkGraph = {
63
+ id: 'root',
64
+ layoutOptions: {
65
+ 'elk.algorithm': 'layered',
66
+ 'elk.direction': 'DOWN',
67
+ 'elk.spacing.nodeNode': '40',
68
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '60',
69
+ 'elk.padding': '[top=40,left=40,bottom=40,right=40]',
70
+ },
71
+ children: snapshot.nodes.map(n => ({
72
+ id: n.id,
73
+ width: NODE_WIDTH,
74
+ height: NODE_HEIGHT,
75
+ })),
76
+ edges: snapshot.edges.map(e => ({
77
+ id: e.id,
78
+ sources: [e.sourceId],
79
+ targets: [e.targetId],
80
+ })),
81
+ };
82
+
83
+ const result = await elk.layout(elkGraph);
84
+
85
+ const positions = new Map<string, NodeLayout>();
86
+ for (const child of result.children ?? []) {
87
+ if (child.x !== undefined && child.y !== undefined) {
88
+ positions.set(child.id, {
89
+ id: child.id,
90
+ x: child.x,
91
+ y: child.y,
92
+ width: child.width ?? NODE_WIDTH,
93
+ height: child.height ?? NODE_HEIGHT,
94
+ });
95
+ }
96
+ }
97
+
98
+ // Fall back: place unmapped nodes in a grid (ELK dropped them — shouldn't happen)
99
+ let col = 0;
100
+ for (const n of snapshot.nodes) {
101
+ if (!positions.has(n.id)) {
102
+ positions.set(n.id, {
103
+ id: n.id,
104
+ x: 40 + col * (NODE_WIDTH + 40),
105
+ y: 40,
106
+ width: NODE_WIDTH,
107
+ height: NODE_HEIGHT,
108
+ });
109
+ col++;
110
+ }
111
+ }
112
+
113
+ const bfsOrder = computeBfsOrder(snapshot.nodes, snapshot.edges, snapshot.focusNodeId);
114
+
115
+ return { positions, bfsOrder };
116
+ }
117
+
118
+ // ── Worker message loop ───────────────────────────────────────────────────────
119
+
120
+ self.onmessage = async (event: MessageEvent<WorkerIn>) => {
121
+ const msg = event.data;
122
+ if (msg.type !== 'layout') return;
123
+
124
+ const result = await runLayout(msg.snapshot, msg.canvasWidth, msg.canvasHeight);
125
+
126
+ const out: WorkerOut = { type: 'layout_result', result };
127
+ self.postMessage(out);
128
+ };
@@ -0,0 +1,102 @@
1
+ // N1 Flow Canvas — Shared Types
2
+ // Pure TypeScript — no Svelte, no DOM dependencies
3
+ // S-02: all collections use Map/Array with deterministic ordering (no Set/Object spread iteration)
4
+
5
+ // === Graph Primitives ===
6
+
7
+ export interface GraphNode {
8
+ id: string;
9
+ label: string;
10
+ /** Optional semantic data (task status, flow phase, etc.) */
11
+ data: Record<string, unknown>;
12
+ }
13
+
14
+ export interface GraphEdge {
15
+ id: string;
16
+ sourceId: string;
17
+ targetId: string;
18
+ /** Optional: task wt-NNN dependency references */
19
+ label?: string;
20
+ }
21
+
22
+ // === GraphSnapshot — readonly, fed to ELK worker, never mutated during layout ===
23
+
24
+ export interface GraphSnapshot {
25
+ readonly nodes: ReadonlyArray<GraphNode>;
26
+ readonly edges: ReadonlyArray<GraphEdge>;
27
+ /** Node id used as layout/BFS root */
28
+ readonly focusNodeId: string | null;
29
+ }
30
+
31
+ // === Computed Layout ===
32
+
33
+ export interface NodeLayout {
34
+ id: string;
35
+ x: number;
36
+ y: number;
37
+ width: number;
38
+ height: number;
39
+ }
40
+
41
+ export interface LayoutResult {
42
+ /** Euclidean positions from ELK, keyed by node id */
43
+ positions: Map<string, NodeLayout>;
44
+ /** BFS traversal order starting from focusNodeId (or arbitrary root if null) */
45
+ bfsOrder: ReadonlyArray<string>;
46
+ }
47
+
48
+ // === Worker Protocol ===
49
+
50
+ export interface WorkerIn {
51
+ type: 'layout';
52
+ snapshot: GraphSnapshot;
53
+ /** Canvas dimensions for ELK bounds */
54
+ canvasWidth: number;
55
+ canvasHeight: number;
56
+ }
57
+
58
+ export interface WorkerOut {
59
+ type: 'layout_result';
60
+ result: LayoutResult;
61
+ }
62
+
63
+ export type WorkerMessage = WorkerIn | WorkerOut;
64
+
65
+ // === GraphState — mutable, holds Spring-wrapped positions ===
66
+ // Implemented in graph-state.svelte.ts (Svelte 5 runes required)
67
+ // Defined here as an interface for type-checking without Svelte dependency
68
+
69
+ export interface GraphStateApi {
70
+ /** Current node positions (Spring targets). Read these for rendering. */
71
+ readonly positions: Map<string, NodeLayout>;
72
+ /** Current BFS order for edge reveal animation */
73
+ readonly bfsOrder: ReadonlyArray<string>;
74
+ /** Update Spring targets from a completed layout. Does NOT reset current positions. */
75
+ applyLayout(result: LayoutResult): void;
76
+ /** Re-center Poincaré disk on this node. Does NOT trigger re-layout. */
77
+ setFocusCenter(nodeId: string): void;
78
+ /** Mark ELK layout root. Triggers worker re-compute. */
79
+ setLayoutRoot(nodeId: string): void;
80
+ }
81
+
82
+ // === Edge Reveal Animation ===
83
+
84
+ export interface EdgeRevealState {
85
+ /** Edge id → current lineDashOffset (0.0 = fully drawn, 1.0 = hidden) */
86
+ offsets: Map<string, number>;
87
+ }
88
+
89
+ /** Stagger delay per BFS wave step (ms). Deterministic — no Math.random. */
90
+ export const EDGE_REVEAL_STAGGER_MS = 30;
91
+
92
+ // === Geometry (shared with n0, re-exported for convenience) ===
93
+
94
+ export interface Point {
95
+ x: number;
96
+ y: number;
97
+ }
98
+
99
+ export interface Size {
100
+ width: number;
101
+ height: number;
102
+ }
@@ -0,0 +1,146 @@
1
+ // N1 Flow Canvas — Generic graph adapter utilities
2
+ // These are the reusable algorithms; apps supply their own domain types + config.
3
+ // S-02: all collections use Map/Array — no Set iteration order assumptions
4
+
5
+ import type { GraphEdge, NodeLayout } from './types.js';
6
+
7
+ // ── Hit testing ───────────────────────────────────────────────────────────────
8
+
9
+ /**
10
+ * Returns the id of the first node whose layout rect contains (x, y), or null.
11
+ *
12
+ * When the canvas element is scaled (CSS width ≠ canvas pixel width), pass
13
+ * scaleX/scaleY to convert event coordinates before testing.
14
+ *
15
+ * @param x - click x in canvas pixel space
16
+ * @param y - click y in canvas pixel space
17
+ * @param positions - GraphState.positions map
18
+ */
19
+ export function nodeHitTest(
20
+ x: number,
21
+ y: number,
22
+ positions: Map<string, NodeLayout>,
23
+ ): string | null {
24
+ for (const [id, layout] of positions) {
25
+ if (
26
+ x >= layout.x &&
27
+ x <= layout.x + layout.width &&
28
+ y >= layout.y &&
29
+ y <= layout.y + layout.height
30
+ ) {
31
+ return id;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+
37
+ /**
38
+ * Convert a DOM click event on a container element to canvas pixel coordinates,
39
+ * accounting for CSS scaling between container size and canvas resolution.
40
+ */
41
+ export function domEventToCanvasCoords(
42
+ event: MouseEvent,
43
+ canvasWidth: number,
44
+ canvasHeight: number,
45
+ ): { x: number; y: number } {
46
+ const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
47
+ const scaleX = canvasWidth / rect.width;
48
+ const scaleY = canvasHeight / rect.height;
49
+ return {
50
+ x: (event.clientX - rect.left) * scaleX,
51
+ y: (event.clientY - rect.top) * scaleY,
52
+ };
53
+ }
54
+
55
+ // ── Edge inference ────────────────────────────────────────────────────────────
56
+
57
+ /**
58
+ * Parse directed dependency edges by scanning each item's description for
59
+ * regex matches that reference another item's id.
60
+ *
61
+ * Generic — works for any item type. Apps supply:
62
+ * - `idFn` — extract the item's own id
63
+ * - `descFn` — extract the text to scan (description, notes, etc.)
64
+ * - `mentionRegex` — must have one capture group yielding the referenced id
65
+ * - `mentionToId` — convert regex capture to a canonical id (e.g. `m => 'wt-' + m`)
66
+ *
67
+ * @example KOS usage:
68
+ * parseMentionEdges(tasks, t => t.id, t => t.description ?? '', /wt-(\d+)/g, m => `wt-${m}`)
69
+ */
70
+ export function parseMentionEdges<T>(
71
+ items: T[],
72
+ idFn: (item: T) => string,
73
+ descFn: (item: T) => string,
74
+ mentionRegex: RegExp,
75
+ mentionToId: (capture: string) => string,
76
+ edgesMap: Map<string, GraphEdge> = new Map(),
77
+ ): Map<string, GraphEdge> {
78
+ const knownIds = new Set(items.map(idFn));
79
+
80
+ for (const item of items) {
81
+ const sourceId = idFn(item);
82
+ const text = descFn(item);
83
+ if (!text) continue;
84
+
85
+ for (const match of text.matchAll(mentionRegex)) {
86
+ const targetId = mentionToId(match[1]);
87
+ if (!knownIds.has(targetId)) continue;
88
+
89
+ const edgeId = `${sourceId}->${targetId}`;
90
+ if (edgesMap.has(edgeId)) continue;
91
+
92
+ edgesMap.set(edgeId, {
93
+ id: edgeId,
94
+ sourceId,
95
+ targetId,
96
+ label: 'depends_on',
97
+ });
98
+ }
99
+ }
100
+
101
+ return edgesMap;
102
+ }
103
+
104
+ /**
105
+ * Create sequential "related" edges between items that share the same group key,
106
+ * ordered by a sort key. Produces a chain: group[0]→group[1]→group[2]…
107
+ *
108
+ * Generic — apps supply:
109
+ * - `idFn` — extract item id
110
+ * - `groupKeyFn` — items with the same key become a chain (e.g. domain+workspace)
111
+ * - `sortKeyFn` — ordering within each group (e.g. createdAt string, or id)
112
+ *
113
+ * @example KOS usage:
114
+ * parseGroupedSequenceEdges(tasks, t => t.id,
115
+ * t => `${t.domain ?? ''}:${t.workspace ?? ''}`,
116
+ * t => t.createdAt ?? t.id)
117
+ */
118
+ export function parseGroupedSequenceEdges<T>(
119
+ items: T[],
120
+ idFn: (item: T) => string,
121
+ groupKeyFn: (item: T) => string,
122
+ sortKeyFn: (item: T) => string,
123
+ edgesMap: Map<string, GraphEdge> = new Map(),
124
+ ): Map<string, GraphEdge> {
125
+ const sorted = [...items].sort((a, b) => sortKeyFn(a).localeCompare(sortKeyFn(b)));
126
+
127
+ const groups = new Map<string, T[]>();
128
+ for (const item of sorted) {
129
+ const key = groupKeyFn(item);
130
+ if (!groups.has(key)) groups.set(key, []);
131
+ groups.get(key)!.push(item);
132
+ }
133
+
134
+ for (const groupItems of groups.values()) {
135
+ if (groupItems.length < 2) continue;
136
+ for (let i = 0; i < groupItems.length - 1; i++) {
137
+ const sourceId = idFn(groupItems[i]);
138
+ const targetId = idFn(groupItems[i + 1]);
139
+ const edgeId = `${sourceId}->${targetId}`;
140
+ if (edgesMap.has(edgeId)) continue;
141
+ edgesMap.set(edgeId, { id: edgeId, sourceId, targetId, label: 'related' });
142
+ }
143
+ }
144
+
145
+ return edgesMap;
146
+ }