@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,253 @@
1
+ // Content-Proportional BSP — Zone sizing based on Pretext-measured content
2
+ // Zones split proportionally to their content area, not equally
3
+ // Tiny zones become "colored blocks" (indicators) when below minContentThreshold
4
+
5
+ import { measureBlock, type BlockMetrics } from '../x0-enchanted-blocks/core/block-layout.js';
6
+ import type { BspNode, ZoneLeaf, SplitDirection, ZoneTilerConfig } from './zone-tiler-types.js';
7
+
8
+ /** Content metadata attached to zones for proportional sizing */
9
+ export interface ContentZone {
10
+ zone: ZoneLeaf;
11
+ content: string;
12
+ metrics?: BlockMetrics;
13
+ /** Effective area (width × height) for split ratio calculation */
14
+ contentArea: number;
15
+ /** When true, zone renders as compact indicator block */
16
+ isMinimized: boolean;
17
+ }
18
+
19
+ /** Configuration for content-proportional layout */
20
+ export interface ProportionalBspConfig extends ZoneTilerConfig {
21
+ /** Minimum content area (px²) before zone becomes minimized block. Default: 10000 (100×100) */
22
+ minContentThreshold?: number;
23
+ /** Minimum zone dimension even for content-rich zones. Default: 80px */
24
+ absoluteMinZoneSize?: number;
25
+ /** Font for Pretext measurement. Default: system font */
26
+ measureFont?: string;
27
+ /** Max width constraint for content measurement. Default: 400px */
28
+ maxContentWidth?: number;
29
+ }
30
+
31
+ /** Build a BSP tree with splits proportional to content areas */
32
+ export function buildProportionalBsp(
33
+ zones: ContentZone[],
34
+ direction: SplitDirection = 'horizontal',
35
+ config: ProportionalBspConfig = {}
36
+ ): BspNode {
37
+ const { minContentThreshold = 10000 } = config;
38
+
39
+ // Measure all content
40
+ const measured = zones.map(z => ({
41
+ ...z,
42
+ metrics: measureBlock(z.content, config.measureFont, config.maxContentWidth),
43
+ }));
44
+
45
+ // Calculate content areas
46
+ const withAreas = measured.map(z => {
47
+ const area = z.metrics ? z.metrics.idealWidth * z.metrics.height : 0;
48
+ return {
49
+ ...z,
50
+ contentArea: area,
51
+ isMinimized: area < minContentThreshold,
52
+ };
53
+ });
54
+
55
+ // Separate minimized blocks from full zones
56
+ const fullZones = withAreas.filter(z => !z.isMinimized);
57
+ const minimizedBlocks = withAreas.filter(z => z.isMinimized);
58
+
59
+ if (fullZones.length === 0) {
60
+ // All zones minimized — build equal BSP for colored blocks
61
+ return buildEqualBsp(minimizedBlocks.map(z => z.zone), direction);
62
+ }
63
+
64
+ // Build proportional tree for full zones
65
+ let tree = buildWeightedBsp(fullZones, direction);
66
+
67
+ // Add minimized blocks as a separate region if any exist
68
+ if (minimizedBlocks.length > 0) {
69
+ const minimizedTree = buildEqualBsp(minimizedBlocks.map(z => z.zone),
70
+ direction === 'horizontal' ? 'vertical' : 'horizontal');
71
+
72
+ // Split to add minimized region (small portion)
73
+ tree = {
74
+ kind: 'split',
75
+ direction,
76
+ ratio: 0.85, // 85% to full zones, 15% to minimized
77
+ first: tree,
78
+ second: minimizedTree,
79
+ };
80
+ }
81
+
82
+ return tree;
83
+ }
84
+
85
+ /** Build BSP with splits weighted by content area */
86
+ function buildWeightedBsp(zones: ContentZone[], direction: SplitDirection): BspNode {
87
+ if (zones.length === 0) {
88
+ throw new Error('Cannot build BSP from empty zones');
89
+ }
90
+ if (zones.length === 1) {
91
+ return zones[0].zone;
92
+ }
93
+
94
+ // Sort by content area descending
95
+ const sorted = [...zones].sort((a, b) => b.contentArea - a.contentArea);
96
+ const totalArea = sorted.reduce((sum, z) => sum + z.contentArea, 0);
97
+
98
+ // Find split point that balances area on each side
99
+ let accumulated = 0;
100
+ let splitIndex = 0;
101
+ const halfArea = totalArea / 2;
102
+
103
+ for (let i = 0; i < sorted.length; i++) {
104
+ accumulated += sorted[i].contentArea;
105
+ if (accumulated >= halfArea) {
106
+ splitIndex = i + 1;
107
+ break;
108
+ }
109
+ }
110
+
111
+ // Ensure at least one zone on each side
112
+ splitIndex = Math.max(1, Math.min(splitIndex, sorted.length - 1));
113
+
114
+ const firstGroup = sorted.slice(0, splitIndex);
115
+ const secondGroup = sorted.slice(splitIndex);
116
+
117
+ // Calculate split ratio based on area proportions
118
+ const firstArea = firstGroup.reduce((sum, z) => sum + z.contentArea, 0);
119
+ const ratio = firstArea / totalArea;
120
+
121
+ // Recursively build subtrees
122
+ const firstNode = firstGroup.length === 1
123
+ ? firstGroup[0].zone
124
+ : buildWeightedBsp(firstGroup, flipDirection(direction));
125
+
126
+ const secondNode = secondGroup.length === 1
127
+ ? secondGroup[0].zone
128
+ : buildWeightedBsp(secondGroup, flipDirection(direction));
129
+
130
+ return {
131
+ kind: 'split',
132
+ direction,
133
+ ratio: Math.max(0.1, Math.min(0.9, ratio)), // Clamp between 10-90%
134
+ first: firstNode,
135
+ second: secondNode,
136
+ };
137
+ }
138
+
139
+ /** Build equal-split BSP (for minimized blocks) */
140
+ function buildEqualBsp(zones: ZoneLeaf[], direction: SplitDirection): BspNode {
141
+ if (zones.length === 0) {
142
+ throw new Error('Cannot build BSP from empty zones');
143
+ }
144
+ if (zones.length === 1) {
145
+ return zones[0];
146
+ }
147
+
148
+ const mid = Math.floor(zones.length / 2);
149
+ const firstGroup = zones.slice(0, mid);
150
+ const secondGroup = zones.slice(mid);
151
+
152
+ const firstNode = firstGroup.length === 1
153
+ ? firstGroup[0]
154
+ : buildEqualBsp(firstGroup, flipDirection(direction));
155
+
156
+ const secondNode = secondGroup.length === 1
157
+ ? secondGroup[0]
158
+ : buildEqualBsp(secondGroup, flipDirection(direction));
159
+
160
+ return {
161
+ kind: 'split',
162
+ direction,
163
+ ratio: 0.5,
164
+ first: firstNode,
165
+ second: secondNode,
166
+ };
167
+ }
168
+
169
+ /** Alternate split direction */
170
+ function flipDirection(d: SplitDirection): SplitDirection {
171
+ return d === 'horizontal' ? 'vertical' : 'horizontal';
172
+ }
173
+
174
+ /** Calculate zone sizing infographic data */
175
+ export function generateSizingInfographic(
176
+ zones: ContentZone[],
177
+ containerWidth: number,
178
+ containerHeight: number,
179
+ config: ProportionalBspConfig = {}
180
+ ): SizingInfo[] {
181
+ const { minContentThreshold = 10000 } = config;
182
+
183
+ return zones.map(z => {
184
+ const metrics = measureBlock(z.content, config.measureFont, config.maxContentWidth);
185
+ const area = metrics.idealWidth * metrics.height;
186
+ const isMinimized = area < minContentThreshold;
187
+
188
+ // Calculate what fits
189
+ const fits: string[] = [];
190
+ if (metrics.lineCount >= 1) fits.push('Title');
191
+ if (metrics.lineCount >= 3) fits.push('Preview');
192
+ if (metrics.lineCount >= 8) fits.push('Full content');
193
+ if (area > minContentThreshold) fits.push('Edit mode');
194
+
195
+ return {
196
+ zoneId: z.zone.id,
197
+ contentArea: area,
198
+ lines: metrics.lineCount,
199
+ isMinimized,
200
+ fits,
201
+ recommendedSize: isMinimized ? '40×40' : `${Math.round(metrics.idealWidth)}×${Math.round(metrics.height)}`,
202
+ };
203
+ });
204
+ }
205
+
206
+ /** Infographic data for each zone */
207
+ export interface SizingInfo {
208
+ zoneId: string;
209
+ contentArea: number;
210
+ lines: number;
211
+ isMinimized: boolean;
212
+ fits: string[];
213
+ recommendedSize: string;
214
+ }
215
+
216
+ /**
217
+ * Predict proportional split ratio based on actual content areas
218
+ * Used to avoid static 50/50 splits
219
+ */
220
+ export function calculateProportionalRatio(
221
+ firstContent: string,
222
+ secondContent: string,
223
+ config: ProportionalBspConfig = {}
224
+ ): number {
225
+ const m1 = measureBlock(firstContent, config.measureFont, config.maxContentWidth);
226
+ const m2 = measureBlock(secondContent, config.measureFont, config.maxContentWidth);
227
+
228
+ const area1 = m1.idealWidth * m1.height;
229
+ const area2 = m2.idealWidth * m2.height;
230
+
231
+ if (area1 + area2 === 0) return 0.5;
232
+
233
+ const ratio = area1 / (area1 + area2);
234
+ return Math.max(0.1, Math.min(0.9, ratio));
235
+ }
236
+
237
+ import { splitZone } from './zone-tiler-types.js';
238
+
239
+ /**
240
+ * Split a leaf zone into two zones using text measurement to proportionalize the ratio
241
+ */
242
+ export function splitZoneProportional(
243
+ root: BspNode,
244
+ targetId: string,
245
+ direction: SplitDirection,
246
+ newZone: ZoneLeaf,
247
+ targetContent: string,
248
+ newZoneContent: string,
249
+ config?: ProportionalBspConfig
250
+ ): BspNode {
251
+ const ratio = calculateProportionalRatio(targetContent, newZoneContent, config);
252
+ return splitZone(root, targetId, direction, newZone, ratio);
253
+ }
@@ -0,0 +1,102 @@
1
+ // ForceCanvas — d3-force simulation types for spatial content layout
2
+ // S-02: Integer permille for weights. Monotonic counter for tick. No Math.random.
3
+ // S-05: Simulation has bounded iteration count per tick.
4
+
5
+ /** A cell in the force simulation */
6
+ export interface ForceCell {
7
+ id: string;
8
+ label: string;
9
+ /** Content type for visual differentiation */
10
+ contentType: 'text' | 'code' | 'url' | 'image' | 'filepath';
11
+ /** Visual weight — controls rendered size. Integer permille (0–1000). */
12
+ weight: number;
13
+ /** Embedding vector for semantic distance computation. Null = no embedding yet. */
14
+ embedding: number[] | null;
15
+ /** Pinned position (user-placed). Set fx/fy to pin. */
16
+ fx?: number | null;
17
+ fy?: number | null;
18
+ /** Arbitrary data for rendering */
19
+ data?: Record<string, unknown>;
20
+ // d3-force internal (populated by simulation)
21
+ x?: number;
22
+ y?: number;
23
+ vx?: number;
24
+ vy?: number;
25
+ index?: number;
26
+ }
27
+
28
+ /** Semantic link between cells (derived from embedding distance) */
29
+ export interface ForceLink {
30
+ source: string;
31
+ target: string;
32
+ /** Semantic distance (0 = identical, 1 = unrelated). Drives spring length. */
33
+ distance: number;
34
+ }
35
+
36
+ /** ForceCanvas configuration */
37
+ export interface ForceCanvasConfig {
38
+ /** Charge strength (repulsion between cells). Default -80. */
39
+ chargeStrength?: number;
40
+ /** Collision radius padding in px. Default 4. */
41
+ collisionPadding?: number;
42
+ /** Wall force margin in px (repulsion from zone boundary). Default 20. */
43
+ wallMargin?: number;
44
+ /** Wall force strength. Default 0.3. */
45
+ wallStrength?: number;
46
+ /** Minimum spring length (px) for highly similar content. Default 30. */
47
+ minSpringLength?: number;
48
+ /** Maximum spring length (px) for unrelated content. Default 200. */
49
+ maxSpringLength?: number;
50
+ /** Maximum simulation iterations per tick. Default 300. S-05 bound. */
51
+ maxIterations?: number;
52
+ /** Velocity decay (0–1). Default 0.4. Higher = more friction. */
53
+ velocityDecay?: number;
54
+ /** Initial velocity for newly added cells. Default 0.1. */
55
+ entryVelocity?: number;
56
+ }
57
+
58
+ /** Computed cell position for rendering */
59
+ export interface CellPosition {
60
+ cell: ForceCell;
61
+ x: number;
62
+ y: number;
63
+ radius: number;
64
+ }
65
+
66
+ // ── Semantic distance → spring length ──────────────────────────────────────
67
+
68
+ /** Convert cosine similarity to spring length.
69
+ * similarity 1.0 → minSpringLength, similarity 0.0 → maxSpringLength */
70
+ export function semanticSpringLength(
71
+ similarity: number,
72
+ minLength: number,
73
+ maxLength: number
74
+ ): number {
75
+ const clamped = Math.max(0, Math.min(1, similarity));
76
+ return maxLength - clamped * (maxLength - minLength);
77
+ }
78
+
79
+ /** Compute cosine similarity between two embedding vectors.
80
+ * Returns 0 if either vector is null or empty. */
81
+ export function cosineSimilarity(a: number[] | null, b: number[] | null): number {
82
+ if (!a || !b || a.length === 0 || b.length === 0 || a.length !== b.length) return 0;
83
+
84
+ let dot = 0;
85
+ let magA = 0;
86
+ let magB = 0;
87
+ for (let i = 0; i < a.length; i++) {
88
+ dot += a[i] * b[i];
89
+ magA += a[i] * a[i];
90
+ magB += b[i] * b[i];
91
+ }
92
+
93
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
94
+ return denom === 0 ? 0 : dot / denom;
95
+ }
96
+
97
+ /** Weight permille → rendered radius in px */
98
+ export function weightToRadius(permille: number, baseRadius = 8): number {
99
+ // weight 500 → baseRadius, scales sqrt for area proportionality
100
+ const normalized = Math.max(100, Math.min(1000, permille)) / 500;
101
+ return baseRadius * Math.sqrt(normalized);
102
+ }
@@ -0,0 +1,15 @@
1
+ // Spatial primitives — BSP zone tiling, force-directed layout, LOD rendering, snap-zoom camera
2
+ // Consumed by Loge first; available to Atelier and future apps.
3
+
4
+ export * from './zone-tiler-types.js';
5
+ export * from './force-canvas-types.js';
6
+ export * from './lod-types.js';
7
+ export * from './camera-types.js';
8
+ export * from './content-proportional-bsp.js';
9
+
10
+ // Components are imported directly:
11
+ // import ZoneTiler from '@kos/design-system/spatial/ZoneTiler.svelte';
12
+ // import ForceCanvas from '@kos/design-system/spatial/ForceCanvas.svelte';
13
+ // import LodRenderer from '@kos/design-system/spatial/LodRenderer.svelte';
14
+ // import SnapZoomCamera from '@kos/design-system/spatial/SnapZoomCamera.svelte';
15
+ // import BspTilingCanvas from '@kos/design-system/spatial/BspTilingCanvas.svelte';
@@ -0,0 +1,70 @@
1
+ // LodRenderer — Level of Detail types
2
+ // S-02: Integer permille for health/weight. Deterministic tick counter for animation.
3
+ // Pure TypeScript — no Svelte, no DOM.
4
+
5
+ /** Discrete LOD levels */
6
+ export type LodLevel = 0 | 1 | 2 | 3;
7
+
8
+ /** Zoom thresholds for LOD transitions */
9
+ export interface LodThresholds {
10
+ /** Zoom >= l0 → L0 (full). Default 0.8. */
11
+ l0: number;
12
+ /** Zoom >= l1 → L1 (compact). Default 0.4. */
13
+ l1: number;
14
+ /** Zoom >= l2 → L2 (dot/chip). Default 0.15. */
15
+ l2: number;
16
+ /** Zoom < l2 → L3 (pixel field). */
17
+ }
18
+
19
+ /** Default LOD thresholds matching ADR spec */
20
+ export const DEFAULT_LOD_THRESHOLDS: LodThresholds = {
21
+ l0: 0.8,
22
+ l1: 0.4,
23
+ l2: 0.15,
24
+ };
25
+
26
+ /** LOD renderer configuration */
27
+ export interface LodRendererConfig {
28
+ /** Zoom thresholds */
29
+ thresholds?: LodThresholds;
30
+ /** Cross-fade transition duration in ms. Default 300. */
31
+ crossFadeDuration?: number;
32
+ }
33
+
34
+ /** Per-cell LOD state tracked by the renderer */
35
+ export interface CellLodState {
36
+ cellId: string;
37
+ /** Current active LOD level */
38
+ currentLevel: LodLevel;
39
+ /** Previous LOD level (for cross-fade) */
40
+ previousLevel: LodLevel | null;
41
+ /** Transition progress: 0 = showing previous, 1 = showing current. Null if not transitioning. */
42
+ transitionProgress: number | null;
43
+ /** Timestamp when transition started */
44
+ transitionStart: number | null;
45
+ }
46
+
47
+ /** Resolve zoom factor to LOD level */
48
+ export function zoomToLod(zoom: number, thresholds: LodThresholds = DEFAULT_LOD_THRESHOLDS): LodLevel {
49
+ if (zoom >= thresholds.l0) return 0;
50
+ if (zoom >= thresholds.l1) return 1;
51
+ if (zoom >= thresholds.l2) return 2;
52
+ return 3;
53
+ }
54
+
55
+ /** LOD level descriptions for accessibility */
56
+ export const LOD_DESCRIPTIONS: Record<LodLevel, string> = {
57
+ 0: 'Full: editable cell with titlebar and resize handles',
58
+ 1: 'Compact: title, type icon, first-line preview',
59
+ 2: 'Chip: colored circle with abbreviation',
60
+ 3: 'Pixel field: heat-mapped color mosaic',
61
+ };
62
+
63
+ /** L2 chip: 2-letter abbreviation from label */
64
+ export function chipAbbrev(label: string): string {
65
+ const words = label.trim().split(/\s+/);
66
+ if (words.length >= 2) {
67
+ return (words[0][0] + words[1][0]).toUpperCase();
68
+ }
69
+ return label.slice(0, 2).toUpperCase();
70
+ }
@@ -0,0 +1,177 @@
1
+ // ZoneTiler — BSP tree zone layout types
2
+ // S-02: No HashMap/HashSet — use sorted arrays for zone lookup.
3
+ // Pure TypeScript — no Svelte, no DOM.
4
+
5
+ /** Privacy state matching Rust SealState enum */
6
+ export type PrivacyState = 'ouvert' | 'scelle';
7
+
8
+ /** Leaf node — a visible zone */
9
+ export interface ZoneLeaf {
10
+ kind: 'leaf';
11
+ id: string;
12
+ label: string;
13
+ /** Optional tint for zone background. Ouvert zones derive color from content; Scelle uses fixed hazard treatment. */
14
+ colorTint?: string;
15
+ privacyState: PrivacyState;
16
+ }
17
+
18
+ /** Split direction */
19
+ export type SplitDirection = 'horizontal' | 'vertical';
20
+
21
+ /** Internal split node — divides space into two children */
22
+ export interface ZoneSplit {
23
+ kind: 'split';
24
+ direction: SplitDirection;
25
+ /** Divider position as fraction (0–1) of parent dimension */
26
+ ratio: number;
27
+ first: BspNode;
28
+ second: BspNode;
29
+ }
30
+
31
+ /** A BSP tree node: either a leaf zone or a split */
32
+ export type BspNode = ZoneLeaf | ZoneSplit;
33
+
34
+ /** Computed zone rectangle (absolute pixel coords within container) */
35
+ export interface ZoneRect {
36
+ id: string;
37
+ x: number;
38
+ y: number;
39
+ width: number;
40
+ height: number;
41
+ zone: ZoneLeaf;
42
+ }
43
+
44
+ /** Computed divider rectangle for hit-testing and rendering */
45
+ export interface DividerRect {
46
+ /** Path from root to this split (for identity) */
47
+ path: string;
48
+ direction: SplitDirection;
49
+ x: number;
50
+ y: number;
51
+ width: number;
52
+ height: number;
53
+ /** Reference to parent split for ratio mutation */
54
+ splitRef: ZoneSplit;
55
+ }
56
+
57
+ /** ZoneTiler configuration */
58
+ export interface ZoneTilerConfig {
59
+ /** Minimum zone dimension in px. Default 120. */
60
+ minZoneSize?: number;
61
+ /** Divider thickness in px. Default 6. */
62
+ dividerThickness?: number;
63
+ /** Divider hit-area expansion in px (each side). Default 4. */
64
+ dividerHitExpand?: number;
65
+ }
66
+
67
+ // ── BSP utilities ──────────────────────────────────────────────────────────
68
+
69
+ /** Flatten a BSP tree into leaf zones with computed rectangles */
70
+ export function flattenBsp(
71
+ node: BspNode,
72
+ x: number,
73
+ y: number,
74
+ w: number,
75
+ h: number,
76
+ dividerThickness: number
77
+ ): { zones: ZoneRect[]; dividers: DividerRect[] } {
78
+ if (node.kind === 'leaf') {
79
+ return {
80
+ zones: [{ id: node.id, x, y, width: w, height: h, zone: node }],
81
+ dividers: [],
82
+ };
83
+ }
84
+
85
+ const dt = dividerThickness;
86
+ const { direction, ratio, first, second } = node;
87
+
88
+ let firstRect: { x: number; y: number; w: number; h: number };
89
+ let secondRect: { x: number; y: number; w: number; h: number };
90
+ let divider: DividerRect;
91
+
92
+ if (direction === 'horizontal') {
93
+ // Split left/right
94
+ const splitX = x + (w - dt) * ratio;
95
+ firstRect = { x, y, w: splitX - x, h };
96
+ secondRect = { x: splitX + dt, y, w: x + w - splitX - dt, h };
97
+ divider = {
98
+ path: `${x},${y}:h:${ratio.toFixed(4)}`,
99
+ direction,
100
+ x: splitX,
101
+ y,
102
+ width: dt,
103
+ height: h,
104
+ splitRef: node,
105
+ };
106
+ } else {
107
+ // Split top/bottom
108
+ const splitY = y + (h - dt) * ratio;
109
+ firstRect = { x, y, w, h: splitY - y };
110
+ secondRect = { x, y: splitY + dt, w, h: y + h - splitY - dt };
111
+ divider = {
112
+ path: `${x},${y}:v:${ratio.toFixed(4)}`,
113
+ direction,
114
+ x,
115
+ y: splitY,
116
+ width: w,
117
+ height: dt,
118
+ splitRef: node,
119
+ };
120
+ }
121
+
122
+ const a = flattenBsp(first, firstRect.x, firstRect.y, firstRect.w, firstRect.h, dt);
123
+ const b = flattenBsp(second, secondRect.x, secondRect.y, secondRect.w, secondRect.h, dt);
124
+
125
+ return {
126
+ zones: [...a.zones, ...b.zones],
127
+ dividers: [divider, ...a.dividers, ...b.dividers],
128
+ };
129
+ }
130
+
131
+ /** Find a zone leaf by id in the BSP tree */
132
+ export function findZone(node: BspNode, id: string): ZoneLeaf | null {
133
+ if (node.kind === 'leaf') return node.id === id ? node : null;
134
+ return findZone(node.first, id) ?? findZone(node.second, id);
135
+ }
136
+
137
+ /** Create a default single-zone BSP tree */
138
+ export function createDefaultBsp(id = 'main', label = 'Main'): ZoneLeaf {
139
+ return { kind: 'leaf', id, label, privacyState: 'ouvert' };
140
+ }
141
+
142
+ /** Split a leaf zone into two zones. Returns a new BSP tree (immutable). */
143
+ export function splitZone(
144
+ root: BspNode,
145
+ targetId: string,
146
+ direction: SplitDirection,
147
+ newZone: ZoneLeaf,
148
+ ratio = 0.5
149
+ ): BspNode {
150
+ if (root.kind === 'leaf') {
151
+ if (root.id === targetId) {
152
+ return {
153
+ kind: 'split',
154
+ direction,
155
+ ratio,
156
+ first: root,
157
+ second: newZone,
158
+ };
159
+ }
160
+ return root;
161
+ }
162
+ return {
163
+ ...root,
164
+ first: splitZone(root.first, targetId, direction, newZone, ratio),
165
+ second: splitZone(root.second, targetId, direction, newZone, ratio),
166
+ };
167
+ }
168
+
169
+ /** Serialize BSP tree to JSON (for desk persistence) */
170
+ export function serializeBsp(node: BspNode): string {
171
+ return JSON.stringify(node);
172
+ }
173
+
174
+ /** Deserialize BSP tree from JSON */
175
+ export function deserializeBsp(json: string): BspNode {
176
+ return JSON.parse(json) as BspNode;
177
+ }
@@ -0,0 +1,14 @@
1
+ <script lang="ts" module>
2
+ // Stub implementation for vault store
3
+ export const vaultStore = {
4
+ async hasFileChangedExternally(_path: string): Promise<boolean> {
5
+ return false;
6
+ },
7
+ async getFileModifiedTime(_path: string): Promise<number> {
8
+ return Date.now();
9
+ },
10
+ updateFileState(_path: string, _content: string, _modifiedAt: number): void {
11
+ // Stub
12
+ }
13
+ };
14
+ </script>
@@ -0,0 +1,34 @@
1
+ // Connection status tracking
2
+
3
+ export type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'error';
4
+
5
+ export interface ConnectionTracker {
6
+ readonly state: ConnectionState;
7
+ subscribe(fn: (state: ConnectionState) => void): () => void;
8
+ setState(state: ConnectionState): void;
9
+ }
10
+
11
+ /**
12
+ * Track connection status to Tauri backend
13
+ */
14
+ export function createConnectionTracker(): ConnectionTracker {
15
+ let state = $state<ConnectionState>('disconnected');
16
+ const subscribers = new Set<(state: ConnectionState) => void>();
17
+
18
+ function setState(newState: ConnectionState) {
19
+ state = newState;
20
+ subscribers.forEach(fn => fn(state));
21
+ }
22
+
23
+ function subscribe(fn: (state: ConnectionState) => void) {
24
+ subscribers.add(fn);
25
+ fn(state);
26
+ return () => subscribers.delete(fn);
27
+ }
28
+
29
+ return {
30
+ get state() { return state; },
31
+ setState,
32
+ subscribe,
33
+ };
34
+ }