@stephen-netu/design-system 0.2.2 → 0.5.1

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 (229) hide show
  1. package/dist/d0-data-viz/BarChart.test.js +53 -0
  2. package/dist/d0-data-viz/CheckpointBar.test.js +83 -0
  3. package/dist/d0-data-viz/CheckpointPill.svelte +1 -1
  4. package/dist/d0-data-viz/CheckpointPill.test.js +78 -0
  5. package/dist/d0-data-viz/ConnectionHealth.test.js +70 -0
  6. package/dist/d0-data-viz/MetricCard.test.js +57 -0
  7. package/dist/d0-data-viz/SparkLine.test.js +47 -0
  8. package/dist/d0-data-viz/StorageMap.test.js +63 -0
  9. package/dist/d0-data-viz/Timeline.test.js +62 -0
  10. package/dist/d0-data-viz/WorldLineTimeline.test.js +99 -0
  11. package/dist/d0-data-viz/checkpoint-types.js +1 -1
  12. package/dist/d0-data-viz/storage-map-types.d.ts.map +1 -1
  13. package/dist/d0-data-viz/storage-map-types.js +6 -3
  14. package/dist/d0-data-viz/theme-integration.js +1 -1
  15. package/dist/demos/AmbientPrimitivesDemo.svelte +103 -0
  16. package/dist/demos/AmbientPrimitivesDemo.svelte.d.ts +5 -0
  17. package/dist/demos/AmbientPrimitivesDemo.svelte.d.ts.map +1 -0
  18. package/dist/demos/NodepadBlocksShowcase.svelte +2 -1
  19. package/dist/demos/NodepadBlocksShowcase.svelte.d.ts.map +1 -1
  20. package/dist/demos/index.d.ts +1 -0
  21. package/dist/demos/index.d.ts.map +1 -1
  22. package/dist/demos/index.js +1 -0
  23. package/dist/editor/CodeEditor.svelte +83 -26
  24. package/dist/editor/CodeEditor.svelte.d.ts +0 -1
  25. package/dist/editor/CodeEditor.svelte.d.ts.map +1 -1
  26. package/dist/editor/ExternalChangeDialog.svelte +117 -0
  27. package/dist/editor/ExternalChangeDialog.svelte.d.ts +13 -0
  28. package/dist/editor/ExternalChangeDialog.svelte.d.ts.map +1 -0
  29. package/dist/editor/MarkdownEditor.svelte +104 -617
  30. package/dist/editor/MarkdownEditor.svelte.d.ts +1 -1
  31. package/dist/editor/MarkdownEditor.svelte.d.ts.map +1 -1
  32. package/dist/editor/codemirror-config.d.ts +36 -0
  33. package/dist/editor/codemirror-config.d.ts.map +1 -0
  34. package/dist/editor/codemirror-config.js +87 -0
  35. package/dist/editor/components/CorrectionTooltip.svelte +180 -159
  36. package/dist/editor/components/CorrectionTooltip.svelte.d.ts +7 -23
  37. package/dist/editor/components/CorrectionTooltip.svelte.d.ts.map +1 -1
  38. package/dist/editor/extensions/autocomplete.d.ts +2 -2
  39. package/dist/editor/extensions/autocomplete.d.ts.map +1 -1
  40. package/dist/editor/extensions/autocomplete.test.js +121 -0
  41. package/dist/editor/extensions/spellcheck.d.ts +21 -3
  42. package/dist/editor/extensions/spellcheck.d.ts.map +1 -1
  43. package/dist/editor/extensions/spellcheck.js +51 -9
  44. package/dist/editor/extensions/spellcheck.test.js +168 -0
  45. package/dist/editor/extensions/types.d.ts +2 -1
  46. package/dist/editor/extensions/types.d.ts.map +1 -1
  47. package/dist/editor/index.d.ts +2 -1
  48. package/dist/editor/index.d.ts.map +1 -1
  49. package/dist/editor/index.js +1 -0
  50. package/dist/editor/markdown-preview.d.ts +9 -0
  51. package/dist/editor/markdown-preview.d.ts.map +1 -0
  52. package/dist/editor/markdown-preview.js +19 -0
  53. package/dist/editor/markdown-preview.test.js +38 -0
  54. package/dist/editor/useCodeMirror.d.ts +23 -0
  55. package/dist/editor/useCodeMirror.d.ts.map +1 -0
  56. package/dist/editor/useCodeMirror.js +40 -0
  57. package/dist/editor/useFilePersistence.d.ts +32 -0
  58. package/dist/editor/useFilePersistence.d.ts.map +1 -0
  59. package/dist/editor/useFilePersistence.js +117 -0
  60. package/dist/fabric/ai/ChatPanel.svelte +61 -62
  61. package/dist/fabric/ai/ChatPanel.svelte.d.ts.map +1 -1
  62. package/dist/fabric/ai/ResearchPanel.svelte +31 -24
  63. package/dist/fabric/data/FileTree.svelte +195 -0
  64. package/dist/fabric/data/FileTree.svelte.d.ts +19 -0
  65. package/dist/fabric/data/FileTree.svelte.d.ts.map +1 -0
  66. package/dist/fabric/data/FileTree.test.js +127 -0
  67. package/dist/fabric/data/index.d.ts +5 -9
  68. package/dist/fabric/data/index.d.ts.map +1 -1
  69. package/dist/fabric/data/index.js +4 -1
  70. package/dist/fabric/index.d.ts +1 -0
  71. package/dist/fabric/index.d.ts.map +1 -1
  72. package/dist/fabric/index.js +2 -0
  73. package/dist/fabric/input/KeyboardShortcuts.svelte +23 -21
  74. package/dist/fabric/layout/CollapsiblePanel.svelte +8 -10
  75. package/dist/fabric/navigation/ActivityRail.svelte +3 -5
  76. package/dist/fabric/navigation/ActivityRail.svelte.d.ts.map +1 -1
  77. package/dist/g0-generative/components/Text.svelte +38 -0
  78. package/dist/g0-generative/components/Text.svelte.d.ts +9 -0
  79. package/dist/g0-generative/components/Text.svelte.d.ts.map +1 -0
  80. package/dist/g0-generative/index.d.ts +1 -0
  81. package/dist/g0-generative/index.d.ts.map +1 -1
  82. package/dist/g0-generative/index.js +1 -0
  83. package/dist/g0-generative/library/ComponentLibrary.d.ts +4 -0
  84. package/dist/g0-generative/library/ComponentLibrary.d.ts.map +1 -1
  85. package/dist/g0-generative/library/ComponentLibrary.js +10 -0
  86. package/dist/g0-generative/library/ComponentLibrary.test.js +135 -0
  87. package/dist/g0-generative/renderer/OpenUIRenderer.svelte +16 -56
  88. package/dist/g0-generative/renderer/OpenUIRenderer.svelte.d.ts.map +1 -1
  89. package/dist/g0-generative/renderer/OpenUIRenderer.test.js +143 -0
  90. package/dist/g0-geo/SovereignMap.svelte +13 -13
  91. package/dist/g0-geo/SovereignMap.svelte.d.ts.map +1 -1
  92. package/dist/index.d.ts +0 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +3 -1
  95. package/dist/l0-layout/block-writer/BlockContent.svelte +23 -22
  96. package/dist/l0-layout/block-writer/BlockContent.svelte.d.ts.map +1 -1
  97. package/dist/l0-layout/block-writer/BlockContent.test.js +47 -0
  98. package/dist/l0-layout/block-writer/markdown.js +11 -11
  99. package/dist/l0-layout/block-writer/markdown.test.js +81 -0
  100. package/dist/l0-layout/card/StackedCard.svelte +87 -0
  101. package/dist/l0-layout/card/StackedCard.svelte.d.ts +9 -0
  102. package/dist/l0-layout/card/StackedCard.svelte.d.ts.map +1 -0
  103. package/dist/l0-layout/card/StackedCard.test.js +29 -0
  104. package/dist/l0-layout/index.d.ts +1 -0
  105. package/dist/l0-layout/index.d.ts.map +1 -1
  106. package/dist/l0-layout/index.js +1 -0
  107. package/dist/l0-layout/kanban/KanbanBoard.svelte +1 -1
  108. package/dist/l0-layout/kanban/KanbanColumn.svelte +1 -1
  109. package/dist/n1-flow-canvas/FlowCanvas.svelte +50 -6
  110. package/dist/n1-flow-canvas/FlowCanvas.svelte.d.ts.map +1 -1
  111. package/dist/n1-flow-canvas/index.d.ts +2 -0
  112. package/dist/n1-flow-canvas/index.d.ts.map +1 -1
  113. package/dist/n1-flow-canvas/index.js +1 -0
  114. package/dist/n1-flow-canvas/pipeline-adapter.d.ts +37 -0
  115. package/dist/n1-flow-canvas/pipeline-adapter.d.ts.map +1 -0
  116. package/dist/n1-flow-canvas/pipeline-adapter.js +160 -0
  117. package/dist/n2-manifold-disk/ManifoldDisk.svelte +62 -35
  118. package/dist/n2-manifold-disk/ManifoldDisk.svelte.d.ts.map +1 -1
  119. package/dist/n4-adaptive-manifold/AdaptiveManifold.svelte +23 -19
  120. package/dist/n4-adaptive-manifold/AdaptiveManifold.svelte.d.ts.map +1 -1
  121. package/dist/n4-adaptive-manifold/types.d.ts +3 -0
  122. package/dist/n4-adaptive-manifold/types.d.ts.map +1 -1
  123. package/dist/p0-primitives/ThemeProvider.svelte +28 -7
  124. package/dist/p0-primitives/ThemeProvider.svelte.d.ts +5 -0
  125. package/dist/p0-primitives/ThemeProvider.svelte.d.ts.map +1 -1
  126. package/dist/p0-primitives/canvas-theme.d.ts +21 -1
  127. package/dist/p0-primitives/canvas-theme.d.ts.map +1 -1
  128. package/dist/p0-primitives/canvas-theme.js +78 -23
  129. package/dist/p0-primitives/debug.d.ts +6 -0
  130. package/dist/p0-primitives/debug.d.ts.map +1 -0
  131. package/dist/p0-primitives/debug.js +21 -0
  132. package/dist/p0-primitives/theme.d.ts.map +1 -1
  133. package/dist/p0-primitives/theme.js +11 -31
  134. package/dist/p0-primitives/tokens/ambient.css +76 -0
  135. package/dist/p0-primitives/tokens/tokens.css +124 -29
  136. package/dist/p0-primitives/utils/index.d.ts +2 -0
  137. package/dist/p0-primitives/utils/index.d.ts.map +1 -1
  138. package/dist/p0-primitives/utils/index.js +1 -0
  139. package/dist/p0-primitives/utils/reveal.d.ts +15 -0
  140. package/dist/p0-primitives/utils/reveal.d.ts.map +1 -0
  141. package/dist/p0-primitives/utils/reveal.js +29 -0
  142. package/dist/p0-primitives/utils/reveal.test.js +95 -0
  143. package/dist/s0-state/SyncStatus.svelte +1 -1
  144. package/dist/s0-state/index.d.ts +2 -1
  145. package/dist/s0-state/index.d.ts.map +1 -1
  146. package/dist/s0-state/index.js +2 -0
  147. package/dist/s0-state/modal-stack-store.svelte.d.ts +3 -3
  148. package/dist/s0-state/modal-stack-store.svelte.js +2 -2
  149. package/dist/spatial/ForceSimulation.svelte.js +1 -1
  150. package/dist/spatial/ParticleAssembleMark.svelte +285 -0
  151. package/dist/spatial/ParticleAssembleMark.svelte.d.ts +24 -0
  152. package/dist/spatial/ParticleAssembleMark.svelte.d.ts.map +1 -0
  153. package/dist/spatial/ZoneTiler.test.js +90 -0
  154. package/dist/spatial/index.js +1 -0
  155. package/dist/spatial/particle-assemble-math.d.ts +22 -0
  156. package/dist/spatial/particle-assemble-math.d.ts.map +1 -0
  157. package/dist/spatial/particle-assemble-math.js +26 -0
  158. package/dist/spatial/particle-assemble-math.test.js +56 -0
  159. package/dist/t0-transport/substrate.svelte.d.ts.map +1 -1
  160. package/dist/t0-transport/substrate.svelte.js +10 -4
  161. package/dist/u0-primitives/LockIndicator.svelte +16 -14
  162. package/dist/u0-primitives/VerticalToolbar.svelte +24 -21
  163. package/dist/u0-primitives/accordion/Accordion.svelte +8 -7
  164. package/dist/u0-primitives/animated-icon/AnimatedIcon.svelte +3 -3
  165. package/dist/u0-primitives/animated-icon/AnimatedIcon.test.js +82 -0
  166. package/dist/u0-primitives/avatar/Avatar.svelte +14 -14
  167. package/dist/u0-primitives/avatar/Avatar.test.js +81 -0
  168. package/dist/u0-primitives/badge/Badge.svelte +6 -4
  169. package/dist/u0-primitives/badge/ValidityBadge.svelte +4 -27
  170. package/dist/u0-primitives/button/Button.svelte +15 -12
  171. package/dist/u0-primitives/card/Card.svelte +37 -15
  172. package/dist/u0-primitives/card/Card.test.js +86 -0
  173. package/dist/u0-primitives/command-palette/CommandPalette.svelte +48 -43
  174. package/dist/u0-primitives/command-palette/CommandPalette.test.js +202 -0
  175. package/dist/u0-primitives/dropdown/Dropdown.svelte +20 -25
  176. package/dist/u0-primitives/grant-badge/GrantStatusBadge.svelte +2 -2
  177. package/dist/u0-primitives/index.d.ts +6 -0
  178. package/dist/u0-primitives/index.d.ts.map +1 -1
  179. package/dist/u0-primitives/index.js +8 -0
  180. package/dist/u0-primitives/input/Input.svelte +77 -24
  181. package/dist/u0-primitives/input/Input.svelte.d.ts +2 -10
  182. package/dist/u0-primitives/input/Input.svelte.d.ts.map +1 -1
  183. package/dist/u0-primitives/input/Input.test.js +81 -1
  184. package/dist/u0-primitives/live-dot/LiveDot.svelte +56 -0
  185. package/dist/u0-primitives/live-dot/LiveDot.svelte.d.ts +11 -0
  186. package/dist/u0-primitives/live-dot/LiveDot.svelte.d.ts.map +1 -0
  187. package/dist/u0-primitives/live-dot/LiveDot.test.js +41 -0
  188. package/dist/u0-primitives/live-dot/index.d.ts +3 -0
  189. package/dist/u0-primitives/live-dot/index.d.ts.map +1 -0
  190. package/dist/u0-primitives/live-dot/index.js +1 -0
  191. package/dist/u0-primitives/modal/Modal.svelte +22 -19
  192. package/dist/u0-primitives/modal/Modal.test.js +1 -1
  193. package/dist/u0-primitives/spinner/Spinner.svelte +11 -79
  194. package/dist/u0-primitives/spinner/Spinner.svelte.d.ts.map +1 -1
  195. package/dist/u0-primitives/steps/Steps.svelte +18 -19
  196. package/dist/u0-primitives/surface-state/SurfaceState.svelte +7 -7
  197. package/dist/u0-primitives/tabs/Tabs.svelte +13 -11
  198. package/dist/u0-primitives/theme-swatch-group/ThemeSwatchGroup.svelte +94 -0
  199. package/dist/u0-primitives/theme-swatch-group/ThemeSwatchGroup.svelte.d.ts +16 -0
  200. package/dist/u0-primitives/theme-swatch-group/ThemeSwatchGroup.svelte.d.ts.map +1 -0
  201. package/dist/u0-primitives/theme-swatch-group/ThemeSwatchGroup.test.js +48 -0
  202. package/dist/u0-primitives/theme-swatch-group/index.d.ts +3 -0
  203. package/dist/u0-primitives/theme-swatch-group/index.d.ts.map +1 -0
  204. package/dist/u0-primitives/theme-swatch-group/index.js +1 -0
  205. package/dist/u0-primitives/toggle/Toggle.svelte +10 -11
  206. package/dist/u0-primitives/tooltip/Tooltip.svelte +9 -9
  207. package/dist/u0-primitives/web-components/button-component.d.ts +5 -1
  208. package/dist/u0-primitives/web-components/button-component.d.ts.map +1 -1
  209. package/dist/u0-primitives/web-components/button-component.js +72 -33
  210. package/dist/u0-primitives/web-components/index.d.ts +3 -0
  211. package/dist/u0-primitives/web-components/index.d.ts.map +1 -0
  212. package/dist/u0-primitives/web-components/index.js +2 -0
  213. package/dist/u0-primitives/web-components/input-component.d.ts +5 -1
  214. package/dist/u0-primitives/web-components/input-component.d.ts.map +1 -1
  215. package/dist/u0-primitives/web-components/input-component.js +56 -39
  216. package/dist/u0-primitives/web-components/web-components.test.js +96 -0
  217. package/dist/u0-primitives/window-frame/WindowFrame.svelte +87 -0
  218. package/dist/u0-primitives/window-frame/WindowFrame.svelte.d.ts +10 -0
  219. package/dist/u0-primitives/window-frame/WindowFrame.svelte.d.ts.map +1 -0
  220. package/dist/u0-primitives/window-frame/WindowFrame.test.js +31 -0
  221. package/dist/u0-primitives/window-frame/index.d.ts +3 -0
  222. package/dist/u0-primitives/window-frame/index.d.ts.map +1 -0
  223. package/dist/u0-primitives/window-frame/index.js +1 -0
  224. package/dist/x0-enchanted-blocks/components/NodepadBlock.svelte +11 -2
  225. package/dist/x0-enchanted-blocks/components/NodepadBlock.svelte.d.ts.map +1 -1
  226. package/package.json +44 -28
  227. package/src/p0-primitives/tokens/ambient.css +76 -0
  228. package/src/p0-primitives/tokens/tokens.css +124 -29
  229. package/src/p0-primitives/tokens/tokens.json +42 -24
@@ -0,0 +1,53 @@
1
+ import { afterEach, describe, it, expect } from 'vitest';
2
+ import { cleanup, render } from '@testing-library/svelte';
3
+ import BarChart from './BarChart.svelte';
4
+ const sampleSeries = [
5
+ { id: 's1', label: 'Alpha', data: [{ label: 'A', value: 10 }, { label: 'B', value: 20 }], color: '#b87333' },
6
+ ];
7
+ describe('BarChart', () => {
8
+ afterEach(() => cleanup());
9
+ it('renders an svg with role img', () => {
10
+ const { container } = render(BarChart, { props: { series: sampleSeries } });
11
+ const svg = container.querySelector('svg[role="img"]');
12
+ expect(svg).not.toBeNull();
13
+ });
14
+ it('renders bars for each data point', () => {
15
+ const { container } = render(BarChart, { props: { series: sampleSeries } });
16
+ expect(container.querySelectorAll('.d0-bar').length).toBe(2);
17
+ });
18
+ it('renders a bar with the correct color', () => {
19
+ const { container } = render(BarChart, { props: { series: [{ id: 'c', label: 'C', data: [{ label: 'X', value: 5 }], color: '#ff0000' }] } });
20
+ const bar = container.querySelector('.d0-bar');
21
+ expect(bar?.getAttribute('fill')).toBe('#ff0000');
22
+ });
23
+ it('renders grid lines when showGrid is true', () => {
24
+ const { container } = render(BarChart, { props: { series: sampleSeries, showGrid: true } });
25
+ expect(container.querySelectorAll('.d0-grid-line').length).toBeGreaterThan(0);
26
+ });
27
+ it('hides grid lines when showGrid is false', () => {
28
+ const { container } = render(BarChart, { props: { series: sampleSeries, showGrid: false } });
29
+ expect(container.querySelectorAll('.d0-grid-line').length).toBe(0);
30
+ });
31
+ it('swaps axes for horizontal orientation', () => {
32
+ const { container } = render(BarChart, { props: { series: sampleSeries, orientation: 'horizontal' } });
33
+ const svg = container.querySelector('svg');
34
+ expect(svg).not.toBeNull();
35
+ expect(container.querySelectorAll('.d0-bar').length).toBe(2);
36
+ });
37
+ it('renders axis labels when showLabels is true', () => {
38
+ const { container } = render(BarChart, { props: { series: sampleSeries, showLabels: true } });
39
+ const labels = container.querySelectorAll('.d0-axis-label');
40
+ expect(labels.length).toBeGreaterThan(0);
41
+ });
42
+ it('still renders with empty series', () => {
43
+ const { container } = render(BarChart, { props: { series: [] } });
44
+ expect(container.querySelector('svg')).not.toBeNull();
45
+ expect(container.querySelectorAll('.d0-bar').length).toBe(0);
46
+ });
47
+ it('renders bar titles with label and value', () => {
48
+ const { container } = render(BarChart, { props: { series: sampleSeries } });
49
+ const title = container.querySelector('.d0-bar title');
50
+ expect(title?.textContent).toContain('A');
51
+ expect(title?.textContent).toContain('10');
52
+ });
53
+ });
@@ -0,0 +1,83 @@
1
+ import { afterEach, describe, it, expect, vi } from 'vitest';
2
+ import { cleanup, render, fireEvent } from '@testing-library/svelte';
3
+ import CheckpointBar from './CheckpointBar.svelte';
4
+ const sampleCheckpoints = [
5
+ { seqno: 1, id: 'cp-1', component: 'ryu-panel', componentType: 'rust-crate', status: 'committed', description: 'Initial panel', timestamp: 1000 },
6
+ { seqno: 2, id: 'cp-2', component: 'ui-shell', componentType: 'ui-bundle', status: 'committed', description: 'Shell update', timestamp: 2000 },
7
+ { seqno: 3, id: 'cp-3', component: 'config', componentType: 'config', status: 'system', description: 'Config change', timestamp: 3000 },
8
+ ];
9
+ describe('CheckpointBar', () => {
10
+ afterEach(() => cleanup());
11
+ it('renders the checkpoint bar container', () => {
12
+ const { container } = render(CheckpointBar, {
13
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
14
+ });
15
+ expect(container.querySelector('.checkpoint-bar')).not.toBeNull();
16
+ });
17
+ it('renders a node for each checkpoint', () => {
18
+ const { container } = render(CheckpointBar, {
19
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
20
+ });
21
+ expect(container.querySelectorAll('.checkpoint-node').length).toBe(3);
22
+ });
23
+ it('marks the current checkpoint as active', () => {
24
+ const { container } = render(CheckpointBar, {
25
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 2 },
26
+ });
27
+ const nodes = container.querySelectorAll('.checkpoint-node');
28
+ expect(nodes[1].classList.contains('active')).toBe(true);
29
+ expect(nodes[0].classList.contains('active')).toBe(false);
30
+ });
31
+ it('calls onCheckpointClick when a node is clicked', async () => {
32
+ const onCheckpointClick = vi.fn();
33
+ const { container } = render(CheckpointBar, {
34
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1, onCheckpointClick },
35
+ });
36
+ const node = container.querySelector('.checkpoint-node');
37
+ await fireEvent.click(node);
38
+ expect(onCheckpointClick).toHaveBeenCalledTimes(1);
39
+ expect(onCheckpointClick).toHaveBeenCalledWith(sampleCheckpoints[0]);
40
+ });
41
+ it('calls onCheckpointDoubleClick on double click', async () => {
42
+ const onCheckpointDoubleClick = vi.fn();
43
+ const { container } = render(CheckpointBar, {
44
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1, onCheckpointDoubleClick },
45
+ });
46
+ const node = container.querySelector('.checkpoint-node');
47
+ await fireEvent.dblClick(node);
48
+ expect(onCheckpointDoubleClick).toHaveBeenCalledTimes(1);
49
+ });
50
+ it('renders the current badge with seqno', () => {
51
+ const { container } = render(CheckpointBar, {
52
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 2 },
53
+ });
54
+ expect(container.querySelector('.current-badge')?.textContent).toContain('2');
55
+ });
56
+ it('renders preview badge when previewSeqno is set', () => {
57
+ const { container } = render(CheckpointBar, {
58
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1, previewSeqno: 3 },
59
+ });
60
+ expect(container.querySelector('.preview-badge')).not.toBeNull();
61
+ expect(container.textContent).toContain('Previewing checkpoint 3');
62
+ });
63
+ it('marks preview checkpoint with preview class', () => {
64
+ const { container } = render(CheckpointBar, {
65
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1, previewSeqno: 2 },
66
+ });
67
+ const nodes = container.querySelectorAll('.checkpoint-node');
68
+ expect(nodes[1].classList.contains('preview')).toBe(true);
69
+ });
70
+ it('renders a timeline connecting line', () => {
71
+ const { container } = render(CheckpointBar, {
72
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
73
+ });
74
+ expect(container.querySelector('.timeline-line')).not.toBeNull();
75
+ });
76
+ it('renders node symbols for each checkpoint', () => {
77
+ const { container } = render(CheckpointBar, {
78
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
79
+ });
80
+ const symbols = container.querySelectorAll('.node-symbol');
81
+ expect(symbols.length).toBe(3);
82
+ });
83
+ });
@@ -49,7 +49,7 @@
49
49
 
50
50
  // Color for current component type
51
51
  let componentColor = $derived(
52
- displayCheckpoint ? getCheckpointColor(displayCheckpoint.componentType, true) : '#b87333'
52
+ displayCheckpoint ? getCheckpointColor(displayCheckpoint.componentType, true) : 'var(--color-accent, #888)'
53
53
  );
54
54
 
55
55
  // Toggle expanded state
@@ -0,0 +1,78 @@
1
+ import { afterEach, describe, it, expect, vi } from 'vitest';
2
+ import { cleanup, render, fireEvent } from '@testing-library/svelte';
3
+ import CheckpointPill from './CheckpointPill.svelte';
4
+ const sampleCheckpoints = [
5
+ { seqno: 1, id: 'cp-1', component: 'ryu-panel', componentType: 'rust-crate', status: 'committed', description: 'Initial panel', timestamp: 1000 },
6
+ { seqno: 2, id: 'cp-2', component: 'ui-shell', componentType: 'ui-bundle', status: 'committed', description: 'Shell update', timestamp: 2000 },
7
+ ];
8
+ describe('CheckpointPill', () => {
9
+ afterEach(() => cleanup());
10
+ it('renders the pill container', () => {
11
+ const { container } = render(CheckpointPill, {
12
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
13
+ });
14
+ expect(container.querySelector('.checkpoint-pill')).not.toBeNull();
15
+ });
16
+ it('renders collapsed pill with seqno by default', () => {
17
+ const { container } = render(CheckpointPill, {
18
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
19
+ });
20
+ expect(container.querySelector('.pill-seqno')?.textContent).toBe('1');
21
+ });
22
+ it('renders collapsed state initially', () => {
23
+ const { container } = render(CheckpointPill, {
24
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
25
+ });
26
+ expect(container.querySelector('.pill-collapsed')).not.toBeNull();
27
+ expect(container.querySelector('.pill-expanded')).toBeNull();
28
+ });
29
+ it('expands on click', async () => {
30
+ const { container } = render(CheckpointPill, {
31
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
32
+ });
33
+ const pill = container.querySelector('.checkpoint-pill');
34
+ await fireEvent.click(pill);
35
+ expect(container.querySelector('.pill-expanded')).not.toBeNull();
36
+ expect(container.querySelector('.checkpoint-pill')?.classList.contains('expanded')).toBe(true);
37
+ });
38
+ it('renders the CheckpointBar when expanded', async () => {
39
+ const { container } = render(CheckpointPill, {
40
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
41
+ });
42
+ await fireEvent.click(container.querySelector('.checkpoint-pill'));
43
+ expect(container.querySelector('.checkpoint-bar')).not.toBeNull();
44
+ });
45
+ it('collapses when close button is clicked', async () => {
46
+ const { container } = render(CheckpointPill, {
47
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1 },
48
+ });
49
+ await fireEvent.click(container.querySelector('.checkpoint-pill'));
50
+ expect(container.querySelector('.pill-expanded')).not.toBeNull();
51
+ await fireEvent.click(container.querySelector('.pill-close'));
52
+ expect(container.querySelector('.pill-expanded')).toBeNull();
53
+ });
54
+ it('renders preview icon when in preview mode', () => {
55
+ const { container } = render(CheckpointPill, {
56
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1, previewSeqno: 2 },
57
+ });
58
+ expect(container.querySelector('.checkpoint-pill')?.classList.contains('preview')).toBe(true);
59
+ });
60
+ it('calls onCheckpointClick when a checkpoint is selected from expanded bar', async () => {
61
+ const onCheckpointClick = vi.fn();
62
+ const { container } = render(CheckpointPill, {
63
+ props: { checkpoints: sampleCheckpoints, currentSeqno: 1, onCheckpointClick },
64
+ });
65
+ await fireEvent.click(container.querySelector('.checkpoint-pill'));
66
+ const node = container.querySelector('.checkpoint-node');
67
+ if (node) {
68
+ await fireEvent.click(node);
69
+ expect(onCheckpointClick).toHaveBeenCalledTimes(1);
70
+ }
71
+ });
72
+ it('renders with empty checkpoints array', () => {
73
+ const { container } = render(CheckpointPill, {
74
+ props: { checkpoints: [], currentSeqno: 0 },
75
+ });
76
+ expect(container.querySelector('.checkpoint-pill')).not.toBeNull();
77
+ });
78
+ });
@@ -0,0 +1,70 @@
1
+ import { afterEach, describe, it, expect, vi } from 'vitest';
2
+ import { cleanup, render, fireEvent } from '@testing-library/svelte';
3
+ import ConnectionHealth from './ConnectionHealth.svelte';
4
+ const excellentMetrics = {
5
+ latency: 45,
6
+ jitter: 3,
7
+ lossRate: 0,
8
+ quality: 'excellent',
9
+ history: [
10
+ { timestamp: 1, latency: 40, lost: false },
11
+ { timestamp: 2, latency: 45, lost: false },
12
+ { timestamp: 3, latency: 42, lost: false },
13
+ ],
14
+ };
15
+ describe('ConnectionHealth', () => {
16
+ afterEach(() => cleanup());
17
+ it('renders the quality label', () => {
18
+ const { container } = render(ConnectionHealth, { props: { metrics: excellentMetrics } });
19
+ expect(container.querySelector('.health-label')?.textContent).toBe('Excellent');
20
+ });
21
+ it('renders latency in compact view', () => {
22
+ const { container } = render(ConnectionHealth, { props: { metrics: excellentMetrics } });
23
+ expect(container.querySelector('.health-latency')?.textContent).toBe('45ms');
24
+ });
25
+ it('renders a colored indicator dot', () => {
26
+ const { container } = render(ConnectionHealth, { props: { metrics: excellentMetrics } });
27
+ const indicator = container.querySelector('.health-indicator');
28
+ expect(indicator).not.toBeNull();
29
+ expect(indicator.style.backgroundColor).toContain('27ae60');
30
+ });
31
+ it('shows metrics grid when expanded', () => {
32
+ const { container } = render(ConnectionHealth, { props: { metrics: excellentMetrics, expanded: true } });
33
+ expect(container.querySelector('.metrics-grid')).not.toBeNull();
34
+ const text = container.textContent || '';
35
+ expect(text).toContain('Latency');
36
+ expect(text).toContain('Jitter');
37
+ expect(text).toContain('Loss');
38
+ });
39
+ it('calls onRetry when retry button is clicked in poor state', async () => {
40
+ const onRetry = vi.fn();
41
+ const poorMetrics = { ...excellentMetrics, quality: 'poor' };
42
+ const { container } = render(ConnectionHealth, { props: { metrics: poorMetrics, onRetry, expanded: true } });
43
+ const retryBtn = container.querySelector('.retry-button');
44
+ expect(retryBtn).not.toBeNull();
45
+ await fireEvent.click(retryBtn);
46
+ expect(onRetry).toHaveBeenCalledTimes(1);
47
+ });
48
+ it('renders disconnected quality with muted color', () => {
49
+ const { container } = render(ConnectionHealth, {
50
+ props: { metrics: { ...excellentMetrics, quality: 'disconnected', latency: null, jitter: null } },
51
+ });
52
+ expect(container.querySelector('.health-label')?.textContent).toBe('Disconnected');
53
+ });
54
+ it('renders sparkline when showGraph is true and history exists', () => {
55
+ const { container } = render(ConnectionHealth, {
56
+ props: { metrics: excellentMetrics, showGraph: true },
57
+ });
58
+ expect(container.querySelector('.health-sparkline')).not.toBeNull();
59
+ });
60
+ it('hides sparkline when showGraph is false', () => {
61
+ const { container } = render(ConnectionHealth, {
62
+ props: { metrics: excellentMetrics, showGraph: false },
63
+ });
64
+ expect(container.querySelector('.health-sparkline')).toBeNull();
65
+ });
66
+ it('renders with null metrics', () => {
67
+ const { container } = render(ConnectionHealth, { props: { metrics: null } });
68
+ expect(container.querySelector('.health-label')?.textContent).toBe('Unknown');
69
+ });
70
+ });
@@ -0,0 +1,57 @@
1
+ import { afterEach, describe, it, expect } from 'vitest';
2
+ import { cleanup, render } from '@testing-library/svelte';
3
+ import MetricCard from './MetricCard.svelte';
4
+ describe('MetricCard', () => {
5
+ afterEach(() => cleanup());
6
+ it('renders the label and value in default size', () => {
7
+ const { container } = render(MetricCard, { props: { label: 'CPU', value: '42' } });
8
+ expect(container.querySelector('.d0-metric-label')?.textContent).toBe('CPU');
9
+ expect(container.querySelector('.d0-metric-value')?.textContent).toBe('42');
10
+ });
11
+ it('renders unit when provided', () => {
12
+ const { container } = render(MetricCard, { props: { label: 'Temp', value: '72', unit: '°F' } });
13
+ expect(container.querySelector('.d0-metric-unit')?.textContent).toBe('°F');
14
+ });
15
+ it('omits unit when not provided', () => {
16
+ const { container } = render(MetricCard, { props: { label: 'Count', value: '5' } });
17
+ expect(container.querySelector('.d0-metric-unit')).toBeNull();
18
+ });
19
+ it('renders trend indicator with up arrow', () => {
20
+ const { container } = render(MetricCard, {
21
+ label: 'Growth', value: '12', trend: 'up', trendValue: '+5%',
22
+ });
23
+ const trend = container.querySelector('.d0-metric-trend');
24
+ expect(trend).not.toBeNull();
25
+ expect(trend?.classList.contains('trend-up')).toBe(true);
26
+ expect(trend?.textContent).toContain('+5%');
27
+ });
28
+ it('renders trend indicator with down arrow', () => {
29
+ const { container } = render(MetricCard, {
30
+ label: 'Loss', value: '3', trend: 'down', trendValue: '-2%',
31
+ });
32
+ const trend = container.querySelector('.d0-metric-trend');
33
+ expect(trend?.classList.contains('trend-down')).toBe(true);
34
+ });
35
+ it('renders flat trend arrow', () => {
36
+ const { container } = render(MetricCard, {
37
+ label: 'Stable', value: '100', trend: 'flat',
38
+ });
39
+ const trend = container.querySelector('.d0-metric-trend');
40
+ expect(trend?.classList.contains('trend-flat')).toBe(true);
41
+ });
42
+ it('renders inline variant with correct classes', () => {
43
+ const { container } = render(MetricCard, {
44
+ label: 'Net', value: '88', size: 'inline',
45
+ });
46
+ expect(container.querySelector('.d0-metric-inline')).not.toBeNull();
47
+ expect(container.querySelector('.d0-metric-inline__label')?.textContent).toBe('Net');
48
+ expect(container.querySelector('.d0-metric-inline__value')?.textContent).toBe('88');
49
+ });
50
+ it('sets aria-label on trend element', () => {
51
+ const { container } = render(MetricCard, {
52
+ label: 'A', value: '1', trend: 'up', trendValue: '1',
53
+ });
54
+ const trend = container.querySelector('.d0-metric-trend');
55
+ expect(trend?.getAttribute('aria-label')).toBe('trending up');
56
+ });
57
+ });
@@ -0,0 +1,47 @@
1
+ import { afterEach, describe, it, expect } from 'vitest';
2
+ import { cleanup, render } from '@testing-library/svelte';
3
+ import SparkLine from './SparkLine.svelte';
4
+ describe('SparkLine', () => {
5
+ afterEach(() => cleanup());
6
+ it('renders an svg with role img', () => {
7
+ const { container } = render(SparkLine, { props: { data: [1, 5, 3, 8, 2] } });
8
+ const svg = container.querySelector('svg[role="img"]');
9
+ expect(svg).not.toBeNull();
10
+ });
11
+ it('renders a path element for the line', () => {
12
+ const { container } = render(SparkLine, { props: { data: [1, 5, 3, 8, 2] } });
13
+ const path = container.querySelector('.d0-sparkline-path');
14
+ expect(path).not.toBeNull();
15
+ expect(path?.getAttribute('d')).toBeTruthy();
16
+ });
17
+ it('renders empty path for single data point', () => {
18
+ const { container } = render(SparkLine, { props: { data: [42] } });
19
+ const path = container.querySelector('.d0-sparkline-path');
20
+ expect(path?.getAttribute('d')).toBe('');
21
+ });
22
+ it('applies custom width and height via viewBox', () => {
23
+ const { container } = render(SparkLine, { props: { data: [1, 2, 3], width: 200, height: 64 } });
24
+ const svg = container.querySelector('svg');
25
+ expect(svg?.getAttribute('viewBox')).toBe('0 0 200 64');
26
+ });
27
+ it('applies custom color to the path stroke', () => {
28
+ const { container } = render(SparkLine, { props: { data: [1, 2, 3], color: '#ff0000' } });
29
+ const path = container.querySelector('.d0-sparkline-path');
30
+ expect(path?.getAttribute('stroke')).toBe('#ff0000');
31
+ });
32
+ it('applies custom strokeWidth', () => {
33
+ const { container } = render(SparkLine, { props: { data: [1, 2, 3], strokeWidth: 4 } });
34
+ const path = container.querySelector('.d0-sparkline-path');
35
+ expect(path?.getAttribute('stroke-width')).toBe('4');
36
+ });
37
+ it('uses default aria-label of Sparkline', () => {
38
+ const { container } = render(SparkLine, { props: { data: [1, 2] } });
39
+ const svg = container.querySelector('svg');
40
+ expect(svg?.getAttribute('aria-label')).toBe('Sparkline');
41
+ });
42
+ it('accepts custom aria-label', () => {
43
+ const { container } = render(SparkLine, { props: { data: [1, 2], 'aria-label': 'CPU usage' } });
44
+ const svg = container.querySelector('svg');
45
+ expect(svg?.getAttribute('aria-label')).toBe('CPU usage');
46
+ });
47
+ });
@@ -0,0 +1,63 @@
1
+ import { afterEach, describe, it, expect, vi } from 'vitest';
2
+ import { cleanup, render, fireEvent } from '@testing-library/svelte';
3
+ import StorageMap from './StorageMap.svelte';
4
+ const sampleNodes = [
5
+ { path: '/data/kos', bytes: 1024 * 1024 * 500, ownerTier: 'kos', label: 'KOS Core' },
6
+ { path: '/data/ext', bytes: 1024 * 1024 * 200, ownerTier: 'external', label: 'External' },
7
+ { path: '/data/unk', bytes: 1024 * 100, ownerTier: 'unknown', label: 'Unknown' },
8
+ ];
9
+ describe('StorageMap', () => {
10
+ afterEach(() => cleanup());
11
+ it('renders the total bytes label', () => {
12
+ const { container } = render(StorageMap, { props: { nodes: sampleNodes } });
13
+ const total = container.querySelector('.sm-total');
14
+ expect(total).not.toBeNull();
15
+ expect(total?.textContent).toContain('MB');
16
+ });
17
+ it('renders a cell for each node', () => {
18
+ const { container } = render(StorageMap, { props: { nodes: sampleNodes } });
19
+ expect(container.querySelectorAll('.sm-cell').length).toBe(3);
20
+ });
21
+ it('renders tier legend items', () => {
22
+ const { container } = render(StorageMap, { props: { nodes: sampleNodes } });
23
+ const legend = container.querySelector('.sm-legend');
24
+ expect(legend).not.toBeNull();
25
+ expect(legend?.textContent).toContain('KOS');
26
+ expect(legend?.textContent).toContain('External');
27
+ expect(legend?.textContent).toContain('Unknown');
28
+ });
29
+ it('renders empty state when no nodes', () => {
30
+ const { container } = render(StorageMap, { props: { nodes: [] } });
31
+ expect(container.querySelector('.sm-empty')).not.toBeNull();
32
+ expect(container.textContent).toContain('No storage data');
33
+ });
34
+ it('calls onnodeclick when a cell is clicked', async () => {
35
+ const onnodeclick = vi.fn();
36
+ const { container } = render(StorageMap, { props: { nodes: sampleNodes, onnodeclick } });
37
+ const cell = container.querySelector('.sm-cell');
38
+ await fireEvent.click(cell);
39
+ expect(onnodeclick).toHaveBeenCalledTimes(1);
40
+ expect(onnodeclick).toHaveBeenCalledWith(sampleNodes[0]);
41
+ });
42
+ it('applies tier-specific CSS classes to cells', () => {
43
+ const { container } = render(StorageMap, { props: { nodes: sampleNodes } });
44
+ expect(container.querySelector('.sm-cell--kos')).not.toBeNull();
45
+ expect(container.querySelector('.sm-cell--external')).not.toBeNull();
46
+ expect(container.querySelector('.sm-cell--unknown')).not.toBeNull();
47
+ });
48
+ it('renders cell labels inside large enough cells', () => {
49
+ const { container } = render(StorageMap, {
50
+ props: { nodes: sampleNodes, width: 800, height: 600 },
51
+ });
52
+ const labels = container.querySelectorAll('.sm-label');
53
+ expect(labels.length).toBeGreaterThan(0);
54
+ });
55
+ it('applies custom width and height to root element', () => {
56
+ const { container } = render(StorageMap, {
57
+ props: { nodes: sampleNodes, width: 1024, height: 768 },
58
+ });
59
+ const root = container.querySelector('.sm-root');
60
+ expect(root.style.width).toBe('1024px');
61
+ expect(root.style.height).toBe('768px');
62
+ });
63
+ });
@@ -0,0 +1,62 @@
1
+ import { afterEach, describe, it, expect } from 'vitest';
2
+ import { cleanup, render } from '@testing-library/svelte';
3
+ import Timeline from './Timeline.svelte';
4
+ const sampleEntries = [
5
+ { id: 'e1', timestamp: '10:00', label: 'Start', description: 'System boot', color: '#b87333' },
6
+ { id: 'e2', timestamp: '10:05', label: 'Connect', description: 'Peer connected', color: '#5f9ea0' },
7
+ { id: 'e3', timestamp: '10:10', label: 'Sync', description: 'State synced', color: '#e6c470' },
8
+ ];
9
+ describe('Timeline', () => {
10
+ afterEach(() => cleanup());
11
+ it('renders an svg with role img', () => {
12
+ const { container } = render(Timeline, { props: { entries: sampleEntries } });
13
+ const svg = container.querySelector('svg[role="img"]');
14
+ expect(svg).not.toBeNull();
15
+ });
16
+ it('renders a dot for each entry', () => {
17
+ const { container } = render(Timeline, { props: { entries: sampleEntries } });
18
+ expect(container.querySelectorAll('.d0-timeline-dot').length).toBe(3);
19
+ });
20
+ it('renders labels for each entry', () => {
21
+ const { container } = render(Timeline, { props: { entries: sampleEntries } });
22
+ const text = container.textContent || '';
23
+ expect(text).toContain('Start');
24
+ expect(text).toContain('Connect');
25
+ expect(text).toContain('Sync');
26
+ });
27
+ it('renders timestamps', () => {
28
+ const { container } = render(Timeline, { props: { entries: sampleEntries } });
29
+ const text = container.textContent || '';
30
+ expect(text).toContain('10:00');
31
+ expect(text).toContain('10:05');
32
+ expect(text).toContain('10:10');
33
+ });
34
+ it('renders descriptions when provided', () => {
35
+ const { container } = render(Timeline, { props: { entries: sampleEntries } });
36
+ const text = container.textContent || '';
37
+ expect(text).toContain('System boot');
38
+ expect(text).toContain('Peer connected');
39
+ });
40
+ it('renders a connecting line for multiple entries', () => {
41
+ const { container } = render(Timeline, { props: { entries: sampleEntries } });
42
+ expect(container.querySelector('.d0-timeline-line')).not.toBeNull();
43
+ });
44
+ it('omits connecting line for single entry', () => {
45
+ const { container } = render(Timeline, { props: { entries: [sampleEntries[0]] } });
46
+ expect(container.querySelector('.d0-timeline-line')).toBeNull();
47
+ });
48
+ it('renders horizontal orientation with class', () => {
49
+ const { container } = render(Timeline, { props: { entries: sampleEntries, orientation: 'horizontal' } });
50
+ expect(container.querySelector('.d0-timeline.horizontal')).not.toBeNull();
51
+ });
52
+ it('uses custom aria-label', () => {
53
+ const { container } = render(Timeline, { props: { entries: sampleEntries, 'aria-label': 'Build timeline' } });
54
+ const svg = container.querySelector('svg');
55
+ expect(svg?.getAttribute('aria-label')).toBe('Build timeline');
56
+ });
57
+ it('renders dots with custom fill color', () => {
58
+ const { container } = render(Timeline, { props: { entries: [sampleEntries[0]] } });
59
+ const dot = container.querySelector('.d0-timeline-dot');
60
+ expect(dot?.getAttribute('fill')).toBe('#b87333');
61
+ });
62
+ });
@@ -0,0 +1,99 @@
1
+ import { afterEach, describe, it, expect, vi } from 'vitest';
2
+ import { cleanup, render, fireEvent } from '@testing-library/svelte';
3
+ import WorldLineTimeline from './WorldLineTimeline.svelte';
4
+ const sampleData = {
5
+ characters: [
6
+ { id: 'c1', label: 'Alice', color: '#b87333' },
7
+ { id: 'c2', label: 'Bob', color: '#5f9ea0' },
8
+ ],
9
+ events: [
10
+ {
11
+ id: 'e1', label: ['First', 'Contact'], type: 'arrival', eraIndex: 0,
12
+ description: 'Initial encounter', attendance: { c1: 'observed', c2: 'observed' },
13
+ placeLabel: 'Lab',
14
+ },
15
+ {
16
+ id: 'e2', label: ['Conflict'], type: 'conflict', eraIndex: 0,
17
+ description: 'Disagreement', attendance: { c1: 'observed', c2: 'psi' },
18
+ },
19
+ ],
20
+ eras: [
21
+ { label: 'Alpha', xNorm: [0, 0.5], color: 'rgba(184,115,51,0.05)' },
22
+ { label: 'Beta', xNorm: [0.5, 1], color: 'rgba(95,158,160,0.05)' },
23
+ ],
24
+ };
25
+ describe('WorldLineTimeline', () => {
26
+ afterEach(() => cleanup());
27
+ it('renders the container div', () => {
28
+ const { container } = render(WorldLineTimeline, {
29
+ props: { data: sampleData, onEventSelect: vi.fn() },
30
+ });
31
+ expect(container.querySelector('.wl-container')).not.toBeNull();
32
+ });
33
+ it('renders an svg element', () => {
34
+ const { container } = render(WorldLineTimeline, {
35
+ props: { data: sampleData, onEventSelect: vi.fn() },
36
+ });
37
+ expect(container.querySelector('.wl-canvas')).not.toBeNull();
38
+ });
39
+ it('renders character row labels', () => {
40
+ const { container } = render(WorldLineTimeline, {
41
+ props: { data: sampleData, onEventSelect: vi.fn() },
42
+ });
43
+ const text = container.textContent || '';
44
+ expect(text).toContain('Alice');
45
+ expect(text).toContain('Bob');
46
+ });
47
+ it('renders event nodes for each character-event attendance', () => {
48
+ const { container } = render(WorldLineTimeline, {
49
+ props: { data: sampleData, onEventSelect: vi.fn() },
50
+ });
51
+ const nodes = container.querySelectorAll('.wl-node');
52
+ expect(nodes.length).toBeGreaterThan(0);
53
+ });
54
+ it('renders era bands', () => {
55
+ const { container } = render(WorldLineTimeline, {
56
+ props: { data: sampleData, onEventSelect: vi.fn() },
57
+ });
58
+ const text = container.textContent || '';
59
+ expect(text).toContain('Alpha');
60
+ expect(text).toContain('Beta');
61
+ });
62
+ it('calls onEventSelect when a node is clicked', async () => {
63
+ const onEventSelect = vi.fn();
64
+ const { container } = render(WorldLineTimeline, {
65
+ props: { data: sampleData, onEventSelect },
66
+ });
67
+ const node = container.querySelector('.wl-node');
68
+ if (node) {
69
+ await fireEvent.click(node);
70
+ expect(onEventSelect).toHaveBeenCalledTimes(1);
71
+ }
72
+ });
73
+ it('marks selected event with selected class', () => {
74
+ const { container } = render(WorldLineTimeline, {
75
+ props: { data: sampleData, selectedEventId: 'e1', onEventSelect: vi.fn() },
76
+ });
77
+ const selectedNodes = container.querySelectorAll('.wl-node.selected');
78
+ expect(selectedNodes.length).toBeGreaterThan(0);
79
+ });
80
+ it('renders psi nodes with psi class', () => {
81
+ const { container } = render(WorldLineTimeline, {
82
+ props: { data: sampleData, onEventSelect: vi.fn() },
83
+ });
84
+ const psiNodes = container.querySelectorAll('.wl-node.psi');
85
+ expect(psiNodes.length).toBeGreaterThan(0);
86
+ });
87
+ it('renders nothing when characters array is empty', () => {
88
+ const { container } = render(WorldLineTimeline, {
89
+ props: { data: { ...sampleData, characters: [] }, onEventSelect: vi.fn() },
90
+ });
91
+ expect(container.querySelectorAll('.wl-node').length).toBe(0);
92
+ });
93
+ it('renders nothing when events array is empty', () => {
94
+ const { container } = render(WorldLineTimeline, {
95
+ props: { data: { ...sampleData, events: [] }, onEventSelect: vi.fn() },
96
+ });
97
+ expect(container.querySelectorAll('.wl-node').length).toBe(0);
98
+ });
99
+ });
@@ -13,7 +13,7 @@ export const CHECKPOINT_COLORS = {
13
13
  /** Get checkpoint color with fallback for when CSS vars aren't set */
14
14
  export function getCheckpointColor(type, isDark = true) {
15
15
  const fallbacks = {
16
- 'rust-crate': { light: '#8a5a28', dark: '#b87333' },
16
+ 'rust-crate': { light: '#8a5a28', dark: '#b55533' },
17
17
  'ui-bundle': { light: '#2d6b6e', dark: '#5f9ea0' },
18
18
  'config': { light: '#7c6fae', dark: '#a99fd6' },
19
19
  'faculty-state': { light: '#d4a03d', dark: '#e6c470' },
@@ -1 +1 @@
1
- {"version":3,"file":"storage-map-types.d.ts","sourceRoot":"","sources":["../../src/d0-data-viz/storage-map-types.ts"],"names":[],"mappings":"AAIA,wCAAwC;AACxC,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvD,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,mCAAmC;AACnC,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAgBzF,CAAC;AAIF;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAAE,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,gBAAqB,GAC5B,aAAa,CAwDf;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD"}
1
+ {"version":3,"file":"storage-map-types.d.ts","sourceRoot":"","sources":["../../src/d0-data-viz/storage-map-types.ts"],"names":[],"mappings":"AAIA,wCAAwC;AACxC,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvD,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,mCAAmC;AACnC,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAmBzF,CAAC;AAIF;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAAE,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,gBAAqB,GAC5B,aAAa,CAwDf;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD"}