@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
@@ -94,7 +94,7 @@
94
94
  transition: border-color var(--transition-fast), background-color var(--transition-fast), color var(--transition-fast);
95
95
  background: transparent;
96
96
  color: var(--color-text-primary);
97
- padding: 0 var(--space-3);
97
+ padding: 0 var(--ui-pad-x);
98
98
  line-height: 1;
99
99
  }
100
100
 
@@ -142,20 +142,23 @@
142
142
 
143
143
  /* --- Variants --- */
144
144
 
145
+ /* Primary = inverted neutral (high-contrast), NOT an accent fill.
146
+ Locked rule: accent = live edge, never a fill. Accent shows on
147
+ :focus-visible. Reads as primary under any theme and any user accent. */
145
148
  .variant-primary {
146
- background: var(--color-accent);
149
+ background: var(--color-text-primary);
147
150
  color: var(--color-bg-app);
148
- border-color: var(--color-accent);
151
+ border-color: var(--color-text-primary);
149
152
  }
150
153
 
151
154
  .variant-primary:hover:not(:disabled) {
152
- background: var(--color-accent-hover);
153
- border-color: var(--color-accent-hover);
155
+ background: var(--color-text-secondary);
156
+ border-color: var(--color-text-secondary);
154
157
  }
155
158
 
156
159
  .variant-primary.is-pressed:not(:disabled) {
157
- background: var(--color-accent-active);
158
- border-color: var(--color-accent-active);
160
+ background: var(--color-text-secondary);
161
+ border-color: var(--color-text-secondary);
159
162
  }
160
163
 
161
164
  .variant-secondary {
@@ -195,18 +198,18 @@
195
198
  }
196
199
 
197
200
  .variant-danger:hover:not(:disabled) {
198
- background: rgba(193, 74, 74, 0.1);
201
+ background: color-mix(in srgb, var(--color-error) 12%, transparent);
199
202
  }
200
203
 
201
204
  .variant-danger.is-pressed:not(:disabled) {
202
- background: rgba(193, 74, 74, 0.2);
205
+ background: color-mix(in srgb, var(--color-error) 22%, transparent);
203
206
  }
204
207
 
205
208
  /* --- Sizes --- */
206
209
 
207
- .size-sm { height: 2rem; font-size: var(--text-xs); }
208
- .size-md { height: 2.5rem; font-size: var(--text-sm); }
209
- .size-lg { height: 3rem; font-size: var(--text-base); }
210
+ .size-sm { height: calc(var(--ui-control-h) - 4px); font-size: var(--ui-font-sm); }
211
+ .size-md { height: var(--ui-control-h); font-size: var(--ui-font); }
212
+ .size-lg { height: calc(var(--ui-control-h) + 6px); font-size: var(--ui-font); }
210
213
 
211
214
  /* --- Loading Spinner --- */
212
215
 
@@ -96,24 +96,26 @@
96
96
  }
97
97
 
98
98
  .ds-card-header {
99
- padding: var(--space-3) var(--space-4);
99
+ padding: var(--ui-pad-y) var(--ui-pad-x);
100
100
  border-bottom: var(--border-width-thin) solid var(--border-default);
101
+ font-family: var(--font-mono);
101
102
  font-weight: 600;
102
103
  background: var(--color-bg-panel);
103
- font-size: var(--text-sm);
104
+ font-size: var(--ui-label);
104
105
  text-transform: uppercase;
105
- letter-spacing: 0.05em;
106
+ letter-spacing: 0.08em;
107
+ color: var(--color-text-secondary);
106
108
  }
107
109
 
108
110
  .ds-card-content {
109
- padding: var(--space-4);
111
+ padding: var(--ui-pad-x);
110
112
  flex: 1;
111
113
  }
112
114
 
113
115
  .ds-card-footer {
114
- padding: var(--space-3) var(--space-4);
116
+ padding: var(--ui-pad-y) var(--ui-pad-x);
115
117
  border-top: var(--border-width-thin) solid var(--border-default);
116
- background: var(--color-accent-faint);
118
+ background: var(--color-bg-panel);
117
119
  }
118
120
 
119
121
  /* --- Variants --- */
@@ -129,11 +131,12 @@
129
131
  border-color: var(--border-subtle);
130
132
  }
131
133
 
134
+ /* 'glass' kept as an API alias but DE-GLASSED: opaque elevated panel,
135
+ no backdrop-filter. Blur = web2/Material; INSTRUMENT builds depth from
136
+ border + contrast, never frosted translucency. */
132
137
  .variant-glass {
133
- background: rgba(24, 28, 32, 0.6);
134
- backdrop-filter: blur(var(--blur-md));
135
- -webkit-backdrop-filter: blur(var(--blur-md));
136
- border: var(--border-width-thin) solid rgba(255, 255, 255, 0.08);
138
+ background: var(--color-bg-panel-elevated);
139
+ border: var(--border-width-thin) solid var(--border-default);
137
140
  color: var(--color-text-primary);
138
141
  }
139
142
 
@@ -149,10 +152,11 @@
149
152
  );
150
153
  }
151
154
 
155
+ /* Hazard = destructive semantics → FIXED error hue, never the user accent. */
152
156
  .variant-hazard {
153
- background: var(--color-accent-faint);
154
- border-color: var(--color-accent);
155
- color: var(--color-accent);
157
+ background: color-mix(in srgb, var(--color-error) 8%, transparent);
158
+ border-color: var(--color-error);
159
+ color: var(--color-error);
156
160
  }
157
161
 
158
162
  /* --- Interactive --- */
@@ -200,8 +204,26 @@
200
204
  }
201
205
 
202
206
  .is-selected.variant-hazard {
203
- border-color: var(--color-accent);
204
- background: var(--color-accent-subtle);
207
+ border-color: var(--color-error);
208
+ background: color-mix(in srgb, var(--color-error) 14%, transparent);
209
+ }
210
+
211
+ /* Hazard interactive states kept on the fixed error hue — never the user accent. */
212
+ .variant-hazard.is-interactive:hover {
213
+ border-color: var(--color-error);
214
+ background: color-mix(in srgb, var(--color-error) 12%, transparent);
215
+ }
216
+
217
+ .variant-hazard.is-interactive:focus-visible .ds-card-ring,
218
+ .is-selected.variant-hazard .ds-card-ring {
219
+ border-color: var(--color-error);
220
+ background: repeating-linear-gradient(
221
+ 45deg,
222
+ transparent 0,
223
+ transparent var(--border-width-default),
224
+ var(--color-error) var(--border-width-default),
225
+ var(--color-error) var(--blur-md)
226
+ );
205
227
  }
206
228
 
207
229
  @media (prefers-reduced-motion: reduce) {
@@ -0,0 +1,86 @@
1
+ import { afterEach, describe, it, expect, vi } from 'vitest';
2
+ import { cleanup, render, fireEvent } from '@testing-library/svelte';
3
+ import { createRawSnippet } from 'svelte';
4
+ import Card from './Card.svelte';
5
+ const textSnippet = (text) => createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
6
+ describe('Card', () => {
7
+ afterEach(() => cleanup());
8
+ it('renders the ds-card root element', () => {
9
+ const { container } = render(Card);
10
+ expect(container.querySelector('.ds-card')).not.toBeNull();
11
+ });
12
+ it('defaults to the elevated variant', () => {
13
+ const { container } = render(Card);
14
+ expect(container.querySelector('.variant-elevated')).not.toBeNull();
15
+ });
16
+ it('applies the requested variant class', () => {
17
+ const { container } = render(Card, { variant: 'hazard' });
18
+ expect(container.querySelector('.variant-hazard')).not.toBeNull();
19
+ });
20
+ it('applies a custom class', () => {
21
+ const { container } = render(Card, { class: 'my-card' });
22
+ expect(container.querySelector('.my-card')).not.toBeNull();
23
+ });
24
+ it('applies an inline style', () => {
25
+ const { container } = render(Card, { style: 'width: 200px' });
26
+ expect(container.querySelector('.ds-card').getAttribute('style')).toContain('width: 200px');
27
+ });
28
+ it('renders children content', () => {
29
+ const { container } = render(Card, { children: textSnippet('body') });
30
+ const content = container.querySelector('.ds-card-content');
31
+ expect(content).not.toBeNull();
32
+ expect(content.textContent).toContain('body');
33
+ });
34
+ it('renders header and footer snippets', () => {
35
+ const { container } = render(Card, {
36
+ header: textSnippet('head'),
37
+ footer: textSnippet('foot')
38
+ });
39
+ expect(container.querySelector('.ds-card-header').textContent).toContain('head');
40
+ expect(container.querySelector('.ds-card-footer').textContent).toContain('foot');
41
+ });
42
+ it('is not interactive by default (no button role)', () => {
43
+ const { container } = render(Card);
44
+ expect(container.querySelector('[role="button"]')).toBeNull();
45
+ expect(container.querySelector('.is-interactive')).toBeNull();
46
+ });
47
+ it('becomes interactive when an onclick handler is provided', () => {
48
+ const { container } = render(Card, { onclick: vi.fn() });
49
+ const card = container.querySelector('.ds-card');
50
+ expect(card.getAttribute('role')).toBe('button');
51
+ expect(card.getAttribute('tabindex')).toBe('0');
52
+ expect(container.querySelector('.is-interactive')).not.toBeNull();
53
+ });
54
+ it('becomes interactive for the interactive variant without onclick', () => {
55
+ const { container } = render(Card, { variant: 'interactive' });
56
+ expect(container.querySelector('[role="button"]')).not.toBeNull();
57
+ });
58
+ it('calls onclick when an interactive card is clicked', async () => {
59
+ const handleClick = vi.fn();
60
+ const { container } = render(Card, { onclick: handleClick });
61
+ await fireEvent.click(container.querySelector('.ds-card'));
62
+ expect(handleClick).toHaveBeenCalledTimes(1);
63
+ });
64
+ it('triggers onclick on Enter key when interactive', async () => {
65
+ const handleClick = vi.fn();
66
+ const { container } = render(Card, { onclick: handleClick });
67
+ await fireEvent.keyDown(container.querySelector('.ds-card'), { key: 'Enter' });
68
+ expect(handleClick).toHaveBeenCalledTimes(1);
69
+ });
70
+ it('triggers onclick on Space key when interactive', async () => {
71
+ const handleClick = vi.fn();
72
+ const { container } = render(Card, { onclick: handleClick });
73
+ await fireEvent.keyDown(container.querySelector('.ds-card'), { key: ' ' });
74
+ expect(handleClick).toHaveBeenCalledTimes(1);
75
+ });
76
+ it('reflects selection via the is-selected class and aria-pressed', () => {
77
+ const { container } = render(Card, { selected: true, onclick: vi.fn() });
78
+ const card = container.querySelector('.ds-card');
79
+ expect(card.classList.contains('is-selected')).toBe(true);
80
+ expect(card.getAttribute('aria-pressed')).toBe('true');
81
+ });
82
+ it('renders the selection ring when selected', () => {
83
+ const { container } = render(Card, { selected: true });
84
+ expect(container.querySelector('.ds-card-ring')).not.toBeNull();
85
+ });
86
+ });
@@ -396,21 +396,21 @@
396
396
  bottom: 100%;
397
397
  left: 0;
398
398
  right: 0;
399
- margin-bottom: var(--space-2);
400
- background: var(--color-bg-panel-elevated, #2a2a2a);
401
- border: 1px solid var(--border-default, #333);
402
- border-radius: var(--radius-lg, 0.5rem);
403
- box-shadow: var(--shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));
399
+ margin-bottom: 2px;
400
+ background: var(--color-bg-panel-elevated);
401
+ border: var(--border-width-default) solid var(--border-default);
402
+ border-radius: 0;
403
+ box-shadow: none;
404
404
  overflow: hidden;
405
405
  z-index: 100;
406
406
  }
407
-
407
+
408
408
  .input-bar {
409
409
  display: flex;
410
410
  align-items: center;
411
411
  gap: var(--space-2);
412
- padding: var(--space-3) var(--space-4);
413
- border-bottom: 1px solid var(--border-subtle, rgba(255,255,255,0.06));
412
+ padding: var(--ui-pad-y) var(--ui-pad-x);
413
+ border-bottom: 1px solid var(--border-default);
414
414
  }
415
415
 
416
416
  .input-bar.executing {
@@ -448,42 +448,48 @@
448
448
  }
449
449
 
450
450
  .spinner {
451
- width: 16px;
452
- height: 16px;
453
- border: 2px solid var(--border-default);
454
- border-top-color: var(--color-accent, var(--color-accent));
451
+ width: 14px;
452
+ height: 14px;
453
+ border: 1.5px solid var(--border-default);
454
+ border-top-color: var(--color-accent);
455
455
  border-radius: 50%;
456
456
  animation: spin 0.8s linear infinite;
457
457
  }
458
-
458
+
459
459
  @keyframes spin {
460
460
  to { transform: rotate(360deg); }
461
461
  }
462
-
462
+
463
463
  .command-list,
464
464
  .suggestion-list {
465
465
  list-style: none;
466
466
  margin: 0;
467
- padding: var(--space-2) 0;
468
- max-height: 200px;
467
+ padding: 0;
468
+ max-height: 220px;
469
469
  overflow-y: auto;
470
470
  }
471
-
471
+
472
472
  .command-list li,
473
473
  .suggestion-list li {
474
474
  display: flex;
475
475
  align-items: center;
476
476
  gap: var(--space-3);
477
- padding: var(--space-2) var(--space-4);
477
+ height: var(--ui-row-h);
478
+ padding: 0 var(--ui-pad-x);
478
479
  cursor: pointer;
479
- transition: background var(--transition-fast, 120ms);
480
+ border-bottom: 1px solid var(--border-default);
481
+ transition: background var(--transition-fast) linear;
480
482
  }
481
-
483
+
484
+ .command-list li:last-child,
485
+ .suggestion-list li:last-child { border-bottom: 0; }
486
+
482
487
  .command-list li:hover,
483
488
  .command-list li.selected,
484
489
  .suggestion-list li:hover,
485
490
  .suggestion-list li.selected {
486
- background: var(--color-accent-subtle, var(--color-accent-muted));
491
+ background: var(--color-accent-subtle);
492
+ box-shadow: inset 2px 0 0 var(--color-accent);
487
493
  }
488
494
 
489
495
  .command-name {
@@ -512,26 +518,25 @@
512
518
  }
513
519
 
514
520
  .param-chip {
515
- font-size: var(--text-xs);
516
- padding: var(--space-1) var(--space-2);
517
- border-radius: var(--radius-sm);
518
- background: var(--color-bg-panel, #222);
519
- color: var(--color-text-tertiary, #706858);
521
+ font-size: var(--ui-label);
522
+ font-family: var(--font-mono);
523
+ padding: 2px var(--ui-pad-y);
524
+ background: var(--color-bg-panel);
525
+ color: var(--color-text-tertiary);
520
526
  border: 1px solid transparent;
521
527
  }
522
-
528
+
523
529
  .param-chip.required {
524
- border-color: var(--color-accent-muted, var(--color-accent-muted));
530
+ border-color: var(--color-accent-muted);
525
531
  }
526
-
532
+
527
533
  .param-chip.filled {
528
- background: var(--color-accent-subtle, var(--color-accent-muted));
529
- color: var(--color-accent, var(--color-accent));
534
+ background: var(--color-accent-subtle);
535
+ color: var(--color-accent);
530
536
  }
531
-
537
+
532
538
  .param-chip.current {
533
- border-color: var(--color-accent, var(--color-accent));
534
- box-shadow: 0 0 0 1px var(--color-accent);
539
+ border-color: var(--color-accent);
535
540
  }
536
541
 
537
542
  .feedback {
@@ -541,13 +546,13 @@
541
546
  }
542
547
 
543
548
  .feedback.success {
544
- color: var(--color-success, #27ae60);
545
- background: rgba(39, 174, 96, 0.1);
549
+ color: var(--color-success);
550
+ background: color-mix(in srgb, var(--color-success) 10%, transparent);
546
551
  }
547
-
552
+
548
553
  .feedback.error {
549
- color: var(--color-error, #c0392b);
550
- background: rgba(192, 57, 43, 0.1);
554
+ color: var(--color-error);
555
+ background: color-mix(in srgb, var(--color-error) 10%, transparent);
551
556
  }
552
557
 
553
558
  .help {
@@ -560,10 +565,10 @@
560
565
  }
561
566
 
562
567
  kbd {
563
- background: var(--color-bg-canvas, #1a1a1a);
564
- padding: 2px 6px;
565
- border-radius: var(--radius-sm);
568
+ background: var(--color-bg-canvas);
569
+ padding: 1px 5px;
570
+ border: 1px solid var(--border-subtle);
566
571
  font-family: var(--font-mono);
567
- font-size: 0.9em;
572
+ font-size: var(--ui-label);
568
573
  }
569
574
  </style>
@@ -0,0 +1,202 @@
1
+ import { afterEach, describe, it, expect, vi } from 'vitest';
2
+ import { cleanup, render, fireEvent } from '@testing-library/svelte';
3
+ import CommandPalette from './CommandPalette.svelte';
4
+ const sampleCommands = [
5
+ {
6
+ id: 'grove.invite',
7
+ path: ['grove', 'invite'],
8
+ description: 'Invite a user to the grove',
9
+ parameters: [
10
+ { name: 'user', description: 'User to invite', required: true, paramType: 'user' },
11
+ { name: 'role', description: 'Role to assign', required: false, paramType: 'string' },
12
+ ],
13
+ requiredCapabilities: ['grove.manage'],
14
+ contexts: ['grove'],
15
+ },
16
+ {
17
+ id: 'agent.mode',
18
+ path: ['agent', 'mode'],
19
+ description: 'Switch agent mode',
20
+ parameters: [
21
+ { name: 'mode', description: 'Mode name', required: true, paramType: 'choice', choices: ['fast', 'deep'] },
22
+ ],
23
+ requiredCapabilities: [],
24
+ contexts: ['agent'],
25
+ },
26
+ {
27
+ id: 'task.create',
28
+ path: ['task', 'create'],
29
+ description: 'Create a new task',
30
+ parameters: [
31
+ { name: 'title', description: 'Task title', required: true, paramType: 'string' },
32
+ ],
33
+ requiredCapabilities: ['task.write'],
34
+ contexts: ['global'],
35
+ },
36
+ ];
37
+ function renderPalette(props = {}) {
38
+ return render(CommandPalette, {
39
+ props: {
40
+ commands: sampleCommands,
41
+ context: { realmId: 'r1' },
42
+ onExecute: vi.fn().mockResolvedValue({ success: true, message: 'Done' }),
43
+ onClose: vi.fn(),
44
+ ...props,
45
+ },
46
+ });
47
+ }
48
+ async function openPalette(container) {
49
+ await fireEvent.keyDown(window, { key: '/' });
50
+ }
51
+ describe('CommandPalette', () => {
52
+ afterEach(() => cleanup());
53
+ it('does not render when closed', () => {
54
+ const { container } = renderPalette();
55
+ expect(container.querySelector('.command-palette')).toBeNull();
56
+ });
57
+ it('renders the palette container when open', async () => {
58
+ const { container } = renderPalette();
59
+ await openPalette(container);
60
+ expect(container.querySelector('.command-palette')).not.toBeNull();
61
+ });
62
+ it('has role="dialog" on the palette container', async () => {
63
+ const { container } = renderPalette();
64
+ await openPalette(container);
65
+ expect(container.querySelector('.command-palette').getAttribute('role')).toBe('dialog');
66
+ });
67
+ it('shows the command list with commands when open', async () => {
68
+ const { container } = renderPalette();
69
+ await openPalette(container);
70
+ expect(container.querySelectorAll('[role="option"]').length).toBe(3);
71
+ });
72
+ it('renders command list with role="listbox"', async () => {
73
+ const { container } = renderPalette();
74
+ await openPalette(container);
75
+ expect(container.querySelector('[role="listbox"]')).not.toBeNull();
76
+ });
77
+ it('renders each command option with role="option"', async () => {
78
+ const { container } = renderPalette();
79
+ await openPalette(container);
80
+ const options = container.querySelectorAll('[role="option"]');
81
+ expect(options.length).toBe(3);
82
+ });
83
+ it('clicking a command selects it and shows parameter chips', async () => {
84
+ const { container } = renderPalette();
85
+ await openPalette(container);
86
+ const options = container.querySelectorAll('[role="option"]');
87
+ await fireEvent.click(options[0]);
88
+ expect(container.querySelectorAll('.param-chip').length).toBe(2);
89
+ });
90
+ it('shows parameter chips for the active command', async () => {
91
+ const { container } = renderPalette();
92
+ await openPalette(container);
93
+ await fireEvent.click(container.querySelectorAll('[role="option"]')[0]);
94
+ const chips = container.querySelectorAll('.param-chip');
95
+ expect(chips[0].textContent).toContain('user');
96
+ expect(chips[1].textContent).toContain('role');
97
+ });
98
+ it('displays keyboard help text', async () => {
99
+ const { container } = renderPalette();
100
+ await openPalette(container);
101
+ const help = container.querySelector('.help');
102
+ expect(help).not.toBeNull();
103
+ expect(help.textContent).toContain('Enter');
104
+ expect(help.textContent).toContain('Esc');
105
+ });
106
+ it('calls onClose when Escape is pressed in browse mode', async () => {
107
+ const handleClose = vi.fn();
108
+ const { container } = renderPalette({ onClose: handleClose });
109
+ await openPalette(container);
110
+ await fireEvent.keyDown(window, { key: 'Escape' });
111
+ expect(handleClose).toHaveBeenCalledTimes(1);
112
+ });
113
+ it('shows commands when open in browse mode', async () => {
114
+ const { container } = renderPalette();
115
+ await openPalette(container);
116
+ const options = container.querySelectorAll('[role="option"]');
117
+ expect(options.length).toBe(3);
118
+ });
119
+ it('marks required parameters with the required class', async () => {
120
+ const { container } = renderPalette();
121
+ await openPalette(container);
122
+ await fireEvent.click(container.querySelectorAll('[role="option"]')[0]);
123
+ const chips = container.querySelectorAll('.param-chip');
124
+ expect(chips[0].classList.contains('required')).toBe(true);
125
+ });
126
+ it('marks the current parameter with the current class', async () => {
127
+ const { container } = renderPalette();
128
+ await openPalette(container);
129
+ await fireEvent.click(container.querySelectorAll('[role="option"]')[0]);
130
+ const chips = container.querySelectorAll('.param-chip');
131
+ expect(chips[0].classList.contains('current')).toBe(true);
132
+ });
133
+ it('marks a filled parameter with the filled class', async () => {
134
+ const { container } = renderPalette();
135
+ await openPalette(container);
136
+ await fireEvent.click(container.querySelectorAll('[role="option"]')[0]);
137
+ const input = container.querySelector('input');
138
+ await fireEvent.input(input, { target: { value: 'alice' } });
139
+ await fireEvent.keyDown(input, { key: 'Enter' });
140
+ const chips = container.querySelectorAll('.param-chip');
141
+ expect(chips[0].classList.contains('filled')).toBe(true);
142
+ });
143
+ it('shows success feedback after successful command execution', async () => {
144
+ const handleExecute = vi.fn().mockResolvedValue({ success: true, message: 'Invited!' });
145
+ const { container } = renderPalette({ onExecute: handleExecute });
146
+ await openPalette(container);
147
+ await fireEvent.click(container.querySelectorAll('[role="option"]')[0]);
148
+ const input = container.querySelector('input');
149
+ await fireEvent.input(input, { target: { value: 'alice' } });
150
+ await fireEvent.keyDown(input, { key: 'Enter' });
151
+ await new Promise(r => setTimeout(r, 50));
152
+ const feedback = container.querySelector('.feedback');
153
+ expect(feedback).not.toBeNull();
154
+ expect(feedback.classList.contains('success')).toBe(true);
155
+ expect(feedback.textContent).toBe('Invited!');
156
+ });
157
+ it('shows error feedback after failed command execution', async () => {
158
+ const handleExecute = vi.fn().mockResolvedValue({ success: false, message: 'No permission' });
159
+ const { container } = renderPalette({ onExecute: handleExecute });
160
+ await openPalette(container);
161
+ await fireEvent.click(container.querySelectorAll('[role="option"]')[0]);
162
+ const input = container.querySelector('input');
163
+ await fireEvent.input(input, { target: { value: 'alice' } });
164
+ await fireEvent.keyDown(input, { key: 'Enter' });
165
+ await new Promise(r => setTimeout(r, 50));
166
+ const feedback = container.querySelector('.feedback');
167
+ expect(feedback).not.toBeNull();
168
+ expect(feedback.classList.contains('error')).toBe(true);
169
+ expect(feedback.textContent).toBe('No permission');
170
+ });
171
+ it('selects a command on Enter key in browse mode', async () => {
172
+ const { container } = renderPalette();
173
+ await openPalette(container);
174
+ const options = container.querySelectorAll('[role="option"]');
175
+ await fireEvent.keyDown(options[0], { key: 'Enter' });
176
+ expect(container.querySelectorAll('.param-chip').length).toBe(2);
177
+ });
178
+ it('has aria-label on the dialog', async () => {
179
+ const { container } = renderPalette();
180
+ await openPalette(container);
181
+ expect(container.querySelector('.command-palette').getAttribute('aria-label')).toBe('Command palette');
182
+ });
183
+ it('has aria-selected on command options', async () => {
184
+ const { container } = renderPalette();
185
+ await openPalette(container);
186
+ const options = container.querySelectorAll('[role="option"]');
187
+ expect(options[0].getAttribute('aria-selected')).toBe('true');
188
+ expect(options[1].getAttribute('aria-selected')).toBe('false');
189
+ });
190
+ it('renders the slash prefix in browse mode', async () => {
191
+ const { container } = renderPalette();
192
+ await openPalette(container);
193
+ expect(container.querySelector('.slash')).not.toBeNull();
194
+ });
195
+ it('renders command prefix when a command is active', async () => {
196
+ const { container } = renderPalette();
197
+ await openPalette(container);
198
+ await fireEvent.click(container.querySelectorAll('[role="option"]')[0]);
199
+ expect(container.querySelector('.command-prefix')).not.toBeNull();
200
+ expect(container.querySelector('.command-prefix').textContent).toBe('/grove,invite');
201
+ });
202
+ });