@polderlabs/bizar 6.3.0 → 7.0.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 (264) hide show
  1. package/.claude/hooks/sessionend-recall.mjs +3 -3
  2. package/.claude/hooks/worker-suggest.mjs +110 -0
  3. package/.claude/settings.json +9 -0
  4. package/README.md +125 -47
  5. package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-weVnLz3Z.js} +3 -3
  6. package/bizar-dash/dist/assets/EnvVarsSection-weVnLz3Z.js.map +1 -0
  7. package/bizar-dash/dist/assets/MobileChat-BRFjer8p.js +1 -0
  8. package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-BRFjer8p.js.map} +1 -1
  9. package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js +1 -0
  10. package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js.map +1 -0
  11. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
  12. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
  13. package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
  14. package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
  15. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
  16. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
  17. package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
  18. package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
  19. package/bizar-dash/dist/assets/main-BE4Uh9im.css +1 -0
  20. package/bizar-dash/dist/assets/main-DqC58FEZ.js +18 -0
  21. package/bizar-dash/dist/assets/main-DqC58FEZ.js.map +1 -0
  22. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
  23. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js.map +1 -0
  24. package/bizar-dash/dist/assets/mobile-DRZsK3ic.js +1 -0
  25. package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DRZsK3ic.js.map} +1 -1
  26. package/bizar-dash/dist/assets/mobile-layout-BWLtwtfQ.css +1 -0
  27. package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js +2 -0
  28. package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js.map +1 -0
  29. package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
  30. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
  31. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
  32. package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.js +2 -0
  33. package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.js.map +1 -0
  34. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
  35. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
  36. package/bizar-dash/dist/index.html +13 -11
  37. package/bizar-dash/dist/mobile.html +8 -5
  38. package/bizar-dash/src/server/api.mjs +11 -0
  39. package/bizar-dash/src/server/memory-consolidator.mjs +225 -0
  40. package/bizar-dash/src/server/memory-distillation.mjs +296 -0
  41. package/bizar-dash/src/server/routes/distill.mjs +129 -0
  42. package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
  43. package/bizar-dash/src/web/App.tsx +4 -0
  44. package/bizar-dash/src/web/components/Tag.tsx +18 -5
  45. package/bizar-dash/src/web/components/Topbar.tsx +3 -0
  46. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
  47. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
  48. package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
  49. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
  50. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
  51. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
  52. package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
  53. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
  54. package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
  55. package/bizar-dash/src/web/lib/types.ts +25 -0
  56. package/bizar-dash/src/web/mobile/MobileApp.tsx +33 -2
  57. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +2 -2
  58. package/bizar-dash/src/web/mobile/MobileEval.tsx +4 -4
  59. package/bizar-dash/src/web/mobile/MobileMemory.tsx +2 -2
  60. package/bizar-dash/src/web/mobile/MobileSettings.tsx +3 -3
  61. package/bizar-dash/src/web/mobile/MobileTasks.tsx +2 -2
  62. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +3 -3
  63. package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
  64. package/bizar-dash/src/web/styles/main.css +251 -0
  65. package/bizar-dash/src/web/styles/mobile.css +41 -0
  66. package/bizar-dash/src/web/styles/tasks-redesign.css +253 -0
  67. package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
  68. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
  69. package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
  70. package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
  71. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
  72. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
  73. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
  74. package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
  75. package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
  76. package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
  77. package/bizar-dash/src/web/ui/controls/Textarea.tsx +56 -0
  78. package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
  79. package/bizar-dash/src/web/ui/controls/controls.css +476 -0
  80. package/bizar-dash/src/web/ui/controls/index.ts +35 -0
  81. package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
  82. package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
  83. package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
  84. package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
  85. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
  86. package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
  87. package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
  88. package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
  89. package/bizar-dash/src/web/ui/data/data.css +535 -0
  90. package/bizar-dash/src/web/ui/data/index.ts +31 -0
  91. package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
  92. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
  93. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
  94. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
  95. package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
  96. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
  97. package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
  98. package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
  99. package/bizar-dash/src/web/ui/index.ts +56 -0
  100. package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
  101. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
  102. package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
  103. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
  104. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
  105. package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
  106. package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
  107. package/bizar-dash/src/web/ui/layout/ViewHeader.tsx +48 -0
  108. package/bizar-dash/src/web/ui/layout/index.ts +34 -0
  109. package/bizar-dash/src/web/ui/layout/layout.css +464 -0
  110. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
  111. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
  112. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
  113. package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
  114. package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
  115. package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
  116. package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
  117. package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
  118. package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
  119. package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
  120. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
  121. package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
  122. package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
  123. package/bizar-dash/src/web/ui/styles/globals.css +86 -0
  124. package/bizar-dash/src/web/ui/styles/reset.css +125 -0
  125. package/bizar-dash/src/web/ui/styles/tokens.css +208 -0
  126. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
  127. package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
  128. package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
  129. package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
  130. package/bizar-dash/src/web/views/Agents.tsx +516 -268
  131. package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
  132. package/bizar-dash/src/web/views/Overview.tsx +985 -448
  133. package/bizar-dash/src/web/views/Tasks.tsx +271 -273
  134. package/bizar-dash/tests/communication-log.test.tsx +67 -0
  135. package/bizar-dash/tests/dep-graph.test.tsx +40 -0
  136. package/bizar-dash/tests/event-log.test.tsx +61 -0
  137. package/bizar-dash/tests/goal-input.test.tsx +72 -0
  138. package/bizar-dash/tests/goap-planner.test.ts +68 -0
  139. package/bizar-dash/tests/quality-gates.test.tsx +59 -0
  140. package/bizar-dash/tests/setup.ts +31 -1
  141. package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
  142. package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
  143. package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
  144. package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
  145. package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
  146. package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
  147. package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
  148. package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
  149. package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
  150. package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
  151. package/bizar-dash/tests/ui/controls/Textarea.test.tsx +56 -0
  152. package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
  153. package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
  154. package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
  155. package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
  156. package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
  157. package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
  158. package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
  159. package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
  160. package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
  161. package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
  162. package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
  163. package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
  164. package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
  165. package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
  166. package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
  167. package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
  168. package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
  169. package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
  170. package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
  171. package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
  172. package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
  173. package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
  174. package/bizar-dash/tests/ui/layout/ViewHeader.test.tsx +47 -0
  175. package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
  176. package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
  177. package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
  178. package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
  179. package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
  180. package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
  181. package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
  182. package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
  183. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
  184. package/bizar-dash/tests/views/Agents.test.tsx +359 -0
  185. package/bizar-dash/tests/views/Overview.test.tsx +413 -0
  186. package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
  187. package/bizar-dash/vitest.config.ts +3 -1
  188. package/cli/__tests__/cost-gate.test.mjs +301 -0
  189. package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
  190. package/cli/bin.mjs +38 -0
  191. package/cli/commands/claim.mjs +282 -0
  192. package/cli/commands/cost.mjs +279 -0
  193. package/cli/cost-gate.mjs +501 -0
  194. package/cli/feature-list-bridge.mjs +400 -0
  195. package/cli/memory-constants.mjs +17 -0
  196. package/cli/provision-claude.mjs +94 -0
  197. package/cli/worker-dispatcher.mjs +259 -0
  198. package/cli/worker-dispatcher.test.mjs +204 -0
  199. package/config/trigger-patterns.json +186 -0
  200. package/package.json +2 -2
  201. package/packages/sdk/package.json +1 -1
  202. package/packages/sdk/src/agent-registry.ts +304 -0
  203. package/packages/sdk/src/consensus/byzantine.ts +587 -0
  204. package/packages/sdk/src/consensus/index.ts +147 -0
  205. package/packages/sdk/src/consensus/queen.ts +173 -0
  206. package/packages/sdk/src/consensus/types.ts +199 -0
  207. package/packages/sdk/src/federation/audit.ts +156 -0
  208. package/packages/sdk/src/federation/budget.ts +269 -0
  209. package/packages/sdk/src/federation/envelope.ts +144 -0
  210. package/packages/sdk/src/federation/hmac.ts +160 -0
  211. package/packages/sdk/src/federation/index.ts +443 -0
  212. package/packages/sdk/src/federation/pii.ts +241 -0
  213. package/packages/sdk/src/federation/policy.ts +112 -0
  214. package/packages/sdk/src/federation/trust.ts +146 -0
  215. package/packages/sdk/src/index.ts +67 -0
  216. package/packages/sdk/src/mcp/server.ts +351 -1
  217. package/packages/sdk/src/router/codemod-intent.ts +125 -0
  218. package/packages/sdk/src/router/index.ts +247 -0
  219. package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
  220. package/packages/sdk/src/router/memory-distillation.ts +249 -0
  221. package/packages/sdk/src/router/model-router.ts +235 -0
  222. package/packages/sdk/src/router/q-learning-router.ts +304 -0
  223. package/packages/sdk/src/swarm-topology.ts +279 -0
  224. package/packages/sdk/tests/agent-registry.test.ts +285 -0
  225. package/packages/sdk/tests/consensus.test.ts +376 -0
  226. package/packages/sdk/tests/federation/audit.test.ts +164 -0
  227. package/packages/sdk/tests/federation/budget.test.ts +189 -0
  228. package/packages/sdk/tests/federation/envelope.test.ts +120 -0
  229. package/packages/sdk/tests/federation/hmac.test.ts +194 -0
  230. package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
  231. package/packages/sdk/tests/federation/pii.test.ts +106 -0
  232. package/packages/sdk/tests/federation/policy.test.ts +140 -0
  233. package/packages/sdk/tests/federation/trust.test.ts +124 -0
  234. package/packages/sdk/tests/mcp-tools.test.ts +204 -0
  235. package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
  236. package/packages/sdk/tests/model-router.test.mjs +108 -0
  237. package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
  238. package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
  239. package/packages/sdk/tests/router.test.mjs +91 -0
  240. package/packages/sdk/tests/sdk.test.mjs +26 -2
  241. package/packages/sdk/tests/swarm-topology.test.ts +258 -0
  242. package/scripts/bh-full-e2e.mjs +11 -15
  243. package/bizar-dash/dist/assets/EnvVarsSection-B58aiJiE.js.map +0 -1
  244. package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
  245. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
  246. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js.map +0 -1
  247. package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
  248. package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
  249. package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
  250. package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
  251. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
  252. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js.map +0 -1
  253. package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
  254. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
  255. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
  256. package/bizar-dash/dist/assets/mobile-layout-CJnZNLy3.css +0 -1
  257. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
  258. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
  259. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
  260. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
  261. package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
  262. package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
  263. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  264. package/packages/sdk/package-lock.json +0 -32
@@ -0,0 +1,105 @@
1
+ // tests/ui/navigation/CommandPalette.test.tsx — Wave 2B
2
+
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { describe, it, expect, vi } from 'vitest';
6
+ import { CommandPalette } from '../../../src/web/ui/navigation/CommandPalette';
7
+
8
+ const ITEMS = [
9
+ {
10
+ id: 'a1',
11
+ type: 'agent' as const,
12
+ label: 'Odin Agent',
13
+ description: 'Mid-tier orchestrator',
14
+ onSelect: vi.fn(),
15
+ },
16
+ {
17
+ id: 't1',
18
+ type: 'task' as const,
19
+ label: 'Build dashboard',
20
+ onSelect: vi.fn(),
21
+ },
22
+ {
23
+ id: 'm1',
24
+ type: 'mod' as const,
25
+ label: 'Headroom',
26
+ onSelect: vi.fn(),
27
+ },
28
+ ];
29
+
30
+ describe('CommandPalette', () => {
31
+ it('does not render when open is false', () => {
32
+ const { container } = render(
33
+ <CommandPalette open={false} onClose={() => {}} items={ITEMS} />,
34
+ );
35
+ expect(container.querySelector('.bd-cmdk')).toBeNull();
36
+ });
37
+
38
+ it('renders when open is true', () => {
39
+ render(<CommandPalette open={true} onClose={() => {}} items={ITEMS} />);
40
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
41
+ });
42
+
43
+ it('shows all items initially grouped by type', () => {
44
+ render(<CommandPalette open={true} onClose={() => {}} items={ITEMS} />);
45
+ expect(screen.getByText('Odin Agent')).toBeInTheDocument();
46
+ expect(screen.getByText('Build dashboard')).toBeInTheDocument();
47
+ expect(screen.getByText('Headroom')).toBeInTheDocument();
48
+ expect(screen.getByText('Agents')).toBeInTheDocument();
49
+ expect(screen.getByText('Tasks')).toBeInTheDocument();
50
+ expect(screen.getByText('Mods')).toBeInTheDocument();
51
+ });
52
+
53
+ it('filters items by fuzzy match on label/description', async () => {
54
+ render(<CommandPalette open={true} onClose={() => {}} items={ITEMS} />);
55
+ await userEvent.type(
56
+ screen.getByPlaceholderText('Search anything…'),
57
+ 'orchestrator',
58
+ );
59
+ // Only "Odin Agent" (description contains "orchestrator") survives.
60
+ expect(screen.getByText('Odin Agent')).toBeInTheDocument();
61
+ expect(screen.queryByText('Build dashboard')).toBeNull();
62
+ expect(screen.queryByText('Headroom')).toBeNull();
63
+ });
64
+
65
+ it('shows "No results" when no items match', async () => {
66
+ render(<CommandPalette open={true} onClose={() => {}} items={ITEMS} />);
67
+ await userEvent.type(
68
+ screen.getByPlaceholderText('Search anything…'),
69
+ 'zzzzzzz',
70
+ );
71
+ expect(screen.getByText('No results')).toBeInTheDocument();
72
+ });
73
+
74
+ it('arrow keys cycle through visible items', async () => {
75
+ render(<CommandPalette open={true} onClose={() => {}} items={ITEMS} />);
76
+ await userEvent.type(screen.getByPlaceholderText('Search anything…'), 'a');
77
+ // After filtering 'a' we may still see multiple results — just check
78
+ // that pressing ArrowDown moves activeIndex.
79
+ const before = document.querySelector('.bd-cmdk__item--active');
80
+ await userEvent.keyboard('{ArrowDown}');
81
+ const after = document.querySelector('.bd-cmdk__item--active');
82
+ // Active highlight class moved from one item to the next.
83
+ expect(before).not.toBe(after);
84
+ });
85
+
86
+ it('clicking an item triggers onSelect and closes the palette', async () => {
87
+ const onClose = vi.fn();
88
+ render(
89
+ <CommandPalette open={true} onClose={onClose} items={ITEMS} />,
90
+ );
91
+ await userEvent.click(screen.getByText('Headroom'));
92
+ expect(ITEMS[2].onSelect).toHaveBeenCalledTimes(1);
93
+ expect(onClose).toHaveBeenCalled();
94
+ });
95
+
96
+ it('Escape closes the palette', async () => {
97
+ const onClose = vi.fn();
98
+ render(<CommandPalette open={true} onClose={onClose} items={ITEMS} />);
99
+ const dialog = screen.getByRole('dialog');
100
+ dialog.focus();
101
+ // The keydown handler is on the .bd-cmdk wrapper; dispatch from there.
102
+ await userEvent.keyboard('{Escape}');
103
+ expect(onClose).toHaveBeenCalled();
104
+ });
105
+ });
@@ -0,0 +1,55 @@
1
+ // tests/ui/navigation/NavGroup.test.tsx — Wave 2B
2
+
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { describe, it, expect } from 'vitest';
6
+ import { Folder, Settings } from 'lucide-react';
7
+ import { NavGroup } from '../../../src/web/ui/navigation/NavGroup';
8
+
9
+ const ITEMS = [
10
+ { label: 'First', icon: Settings, href: '/first' },
11
+ { label: 'Second', icon: Settings, href: '/second' },
12
+ ];
13
+
14
+ describe('NavGroup', () => {
15
+ it('renders the group label', () => {
16
+ render(<NavGroup label="Workspace" items={ITEMS} />);
17
+ expect(screen.getByText('Workspace')).toBeInTheDocument();
18
+ });
19
+
20
+ it('projects each item through NavLink', () => {
21
+ render(<NavGroup label="W" items={ITEMS} />);
22
+ expect(screen.getByRole('link', { name: /first/i })).toHaveAttribute(
23
+ 'href',
24
+ '/first',
25
+ );
26
+ expect(screen.getByRole('link', { name: /second/i })).toHaveAttribute(
27
+ 'href',
28
+ '/second',
29
+ );
30
+ });
31
+
32
+ it('toggles open/closed on header click', async () => {
33
+ const { container } = render(
34
+ <NavGroup label="W" items={ITEMS} defaultOpen={true} />,
35
+ );
36
+ // Start open
37
+ expect(container.querySelector('.bd-nav-group--open')).toBeInTheDocument();
38
+ // Click to close
39
+ await userEvent.click(screen.getByText('W'));
40
+ expect(container.querySelector('.bd-nav-group--open')).toBeNull();
41
+ // Click to open
42
+ await userEvent.click(screen.getByText('W'));
43
+ expect(container.querySelector('.bd-nav-group--open')).toBeInTheDocument();
44
+ });
45
+
46
+ it('does not toggle when collapsible is false', async () => {
47
+ const { container } = render(
48
+ <NavGroup label="W" items={ITEMS} collapsible={false} />,
49
+ );
50
+ expect(container.querySelector('.bd-nav-group--non-collapsible')).toBeInTheDocument();
51
+ await userEvent.click(screen.getByText('W'));
52
+ // Header click is a no-op when not collapsible.
53
+ expect(container.querySelector('.bd-nav-group--non-collapsible')).toBeInTheDocument();
54
+ });
55
+ });
@@ -0,0 +1,61 @@
1
+ // tests/ui/navigation/NavLink.test.tsx — Wave 2B
2
+
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { describe, it, expect, vi } from 'vitest';
6
+ import { Home } from 'lucide-react';
7
+ import { NavLink } from '../../../src/web/ui/navigation/NavLink';
8
+
9
+ describe('NavLink', () => {
10
+ it('renders an <a> when href is provided', () => {
11
+ render(<NavLink icon={Home} label="Home" href="/home" />);
12
+ const anchor = screen.getByRole('link', { name: /home/i });
13
+ expect(anchor.tagName).toBe('A');
14
+ expect(anchor).toHaveAttribute('href', '/home');
15
+ });
16
+
17
+ it('renders a <button> when only onClick is provided', () => {
18
+ const onClick = vi.fn();
19
+ render(<NavLink icon={Home} label="Trigger" onClick={onClick} />);
20
+ const btn = screen.getByRole('button', { name: /trigger/i });
21
+ expect(btn.tagName).toBe('BUTTON');
22
+ });
23
+
24
+ it('fires onClick when the button is clicked', async () => {
25
+ const onClick = vi.fn();
26
+ render(<NavLink icon={Home} label="X" onClick={onClick} />);
27
+ await userEvent.click(screen.getByRole('button', { name: /x/i }));
28
+ expect(onClick).toHaveBeenCalledTimes(1);
29
+ });
30
+
31
+ it('renders a <div> when neither href nor onClick is provided', () => {
32
+ const { container } = render(<NavLink icon={Home} label="Plain" />);
33
+ const link = container.querySelector('.bd-nav-link');
34
+ expect(link).toBeInTheDocument();
35
+ expect(link?.tagName).toBe('DIV');
36
+ });
37
+
38
+ it('applies active state styling', () => {
39
+ const { container } = render(
40
+ <NavLink icon={Home} label="Active" href="/x" active />,
41
+ );
42
+ expect(container.querySelector('.bd-nav-link--active')).toBeInTheDocument();
43
+ const anchor = screen.getByRole('link');
44
+ expect(anchor).toHaveAttribute('aria-current', 'page');
45
+ });
46
+
47
+ it('renders a badge when supplied', () => {
48
+ render(<NavLink icon={Home} label="Tasks" href="/t" badge={7} />);
49
+ expect(screen.getByText('7')).toBeInTheDocument();
50
+ });
51
+
52
+ it('applies disabled state and blocks navigation', () => {
53
+ const { container } = render(
54
+ <NavLink icon={Home} label="Locked" href="/locked" disabled />,
55
+ );
56
+ const anchor = container.querySelector('a')!;
57
+ expect(anchor).toHaveAttribute('aria-disabled', 'true');
58
+ expect(anchor.getAttribute('href')).toBeNull();
59
+ expect(anchor).toHaveClass('bd-nav-link--disabled');
60
+ });
61
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Box } from '../../../src/web/ui/primitives/Box';
4
+
5
+ describe('Box', () => {
6
+ it('renders a div by default', () => {
7
+ const { container } = render(<Box>hello</Box>);
8
+ const el = container.firstChild as HTMLElement;
9
+ expect(el.tagName).toBe('DIV');
10
+ expect(el).toHaveClass('box');
11
+ });
12
+
13
+ it('renders the requested semantic tag', () => {
14
+ const { container } = render(<Box as="section">x</Box>);
15
+ expect(container.firstChild?.nodeName).toBe('SECTION');
16
+ });
17
+
18
+ it('composes padding/margin/bg/border classes from props', () => {
19
+ const { container } = render(
20
+ <Box p={3} m={2} bg="1" border="subtle" rounded="md" data-testid="b" />,
21
+ );
22
+ const el = screen.getByTestId('b');
23
+ expect(el).toHaveClass('box-p-3', 'box-m-2', 'box-bg-1', 'box-border-subtle', 'box-rounded-md');
24
+ });
25
+
26
+ it('renders zero values without falsy-skip', () => {
27
+ const { container } = render(<Box p={0} m={0} data-testid="b" />);
28
+ const el = screen.getByTestId('b');
29
+ expect(el).toHaveClass('box-p-0', 'box-m-0');
30
+ });
31
+
32
+ it('composes display and position classes', () => {
33
+ const { container } = render(
34
+ <Box display="flex" position="relative" data-testid="b" />,
35
+ );
36
+ const el = screen.getByTestId('b');
37
+ expect(el).toHaveClass('box-d-flex', 'box-pos-relative');
38
+ });
39
+
40
+ it('passes through arbitrary HTML attributes', () => {
41
+ const { container } = render(
42
+ <Box id="x" aria-label="region" data-foo="bar" />,
43
+ );
44
+ const el = container.firstChild as HTMLElement;
45
+ expect(el.id).toBe('x');
46
+ expect(el.getAttribute('aria-label')).toBe('region');
47
+ expect(el.getAttribute('data-foo')).toBe('bar');
48
+ });
49
+ });
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Grid } from '../../../src/web/ui/primitives/Grid';
4
+
5
+ describe('Grid', () => {
6
+ it('renders a grid container with default 1 column', () => {
7
+ render(<Grid data-testid="g" />);
8
+ const el = screen.getByTestId('g');
9
+ expect(el).toHaveClass('grid', 'cols-1');
10
+ });
11
+
12
+ it('applies the requested column count class', () => {
13
+ render(<Grid cols={3} data-testid="g" />);
14
+ expect(screen.getByTestId('g')).toHaveClass('cols-3');
15
+ });
16
+
17
+ it('applies gap class from prop', () => {
18
+ render(<Grid cols={2} gap={5} data-testid="g" />);
19
+ expect(screen.getByTestId('g')).toHaveClass('gap-5');
20
+ });
21
+
22
+ it('switches to autoFit class when autoFit is true', () => {
23
+ render(<Grid autoFit cols={4} data-testid="g" />);
24
+ const el = screen.getByTestId('g');
25
+ expect(el).toHaveClass('grid-autofit');
26
+ expect(el).not.toHaveClass('cols-4');
27
+ });
28
+
29
+ it('renders children inside the grid', () => {
30
+ render(
31
+ <Grid>
32
+ <div data-testid="cell">x</div>
33
+ </Grid>,
34
+ );
35
+ expect(screen.getByTestId('cell')).toBeInTheDocument();
36
+ });
37
+ });
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Inline } from '../../../src/web/ui/primitives/Inline';
4
+
5
+ describe('Inline', () => {
6
+ it('renders as a row with wrap by default', () => {
7
+ const { container } = render(<Inline data-testid="i">a</Inline>);
8
+ const el = screen.getByTestId('i');
9
+ expect(el).toHaveClass('inline', 'wrap');
10
+ expect(el).not.toHaveClass('inline-col');
11
+ });
12
+
13
+ it('uses gap-3 by default', () => {
14
+ render(<Inline data-testid="i" />);
15
+ expect(screen.getByTestId('i')).toHaveClass('gap-3');
16
+ });
17
+
18
+ it('switches direction to column', () => {
19
+ render(<Inline direction="column" data-testid="i" />);
20
+ expect(screen.getByTestId('i')).toHaveClass('inline-col');
21
+ });
22
+
23
+ it('applies nowrap when wrap is false', () => {
24
+ render(<Inline wrap={false} data-testid="i" />);
25
+ expect(screen.getByTestId('i')).toHaveClass('nowrap');
26
+ });
27
+
28
+ it('forwards alignment props', () => {
29
+ render(<Inline align="baseline" justify="end" data-testid="i" />);
30
+ const el = screen.getByTestId('i');
31
+ expect(el).toHaveClass('align-baseline', 'justify-end');
32
+ });
33
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Separator } from '../../../src/web/ui/primitives/Separator';
4
+
5
+ describe('Separator', () => {
6
+ it('renders an <hr> by default in horizontal orientation', () => {
7
+ const { container } = render(<Separator />);
8
+ const hr = container.querySelector('hr');
9
+ expect(hr).toBeInTheDocument();
10
+ expect(hr).toHaveClass('separator');
11
+ });
12
+
13
+ it('applies the vertical orientation class to <hr>', () => {
14
+ const { container } = render(<Separator orientation="vertical" />);
15
+ const hr = container.querySelector('hr');
16
+ expect(hr).toHaveClass('separator-vertical');
17
+ });
18
+
19
+ it('renders a labelled separator as a div with role=separator', () => {
20
+ const { container } = render(<Separator label="OR" />);
21
+ const el = container.querySelector('[role="separator"]');
22
+ expect(el).toBeInTheDocument();
23
+ expect(el).toHaveClass('separator-with-label');
24
+ expect(el).toHaveTextContent('OR');
25
+ });
26
+
27
+ it('sets aria-orientation on the labelled variant', () => {
28
+ const { container } = render(
29
+ <Separator label="AND" orientation="horizontal" />,
30
+ );
31
+ const el = container.querySelector('[role="separator"]');
32
+ expect(el?.getAttribute('aria-orientation')).toBe('horizontal');
33
+ });
34
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Stack } from '../../../src/web/ui/primitives/Stack';
4
+
5
+ describe('Stack', () => {
6
+ it('renders a column by default with gap-3', () => {
7
+ const { container } = render(
8
+ <Stack data-testid="s">
9
+ <span>a</span>
10
+ </Stack>,
11
+ );
12
+ const el = screen.getByTestId('s');
13
+ expect(el).toHaveClass('stack');
14
+ expect(el).toHaveClass('gap-3');
15
+ });
16
+
17
+ it('switches to row direction', () => {
18
+ const { container } = render(<Stack direction="row" data-testid="s" />);
19
+ expect(screen.getByTestId('s')).toHaveClass('stack-row');
20
+ });
21
+
22
+ it('applies alignment and justify modifiers', () => {
23
+ const { container } = render(
24
+ <Stack align="center" justify="between" data-testid="s" />,
25
+ );
26
+ const el = screen.getByTestId('s');
27
+ expect(el).toHaveClass('align-center', 'justify-between');
28
+ });
29
+
30
+ it('enables flex-wrap when wrap is true', () => {
31
+ const { container } = render(<Stack wrap data-testid="s" />);
32
+ expect(screen.getByTestId('s')).toHaveClass('wrap');
33
+ });
34
+
35
+ it('renders children inside the flex container', () => {
36
+ render(
37
+ <Stack>
38
+ <span data-testid="c1">a</span>
39
+ <span data-testid="c2">b</span>
40
+ </Stack>,
41
+ );
42
+ expect(screen.getByTestId('c1')).toBeInTheDocument();
43
+ expect(screen.getByTestId('c2')).toBeInTheDocument();
44
+ });
45
+ });
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { VisuallyHidden } from '../../../src/web/ui/primitives/VisuallyHidden';
4
+
5
+ describe('VisuallyHidden', () => {
6
+ it('renders its children inside a span', () => {
7
+ render(<VisuallyHidden>hidden text</VisuallyHidden>);
8
+ const el = screen.getByText('hidden text');
9
+ expect(el.tagName).toBe('SPAN');
10
+ });
11
+
12
+ it('applies the vh class for SR-only styling', () => {
13
+ render(<VisuallyHidden>sr-only</VisuallyHidden>);
14
+ expect(screen.getByText('sr-only')).toHaveClass('vh');
15
+ });
16
+
17
+ it('passes additional attributes through to the span', () => {
18
+ render(
19
+ <VisuallyHidden id="vh-1" data-testid="vh">
20
+ x
21
+ </VisuallyHidden>,
22
+ );
23
+ const el = screen.getByTestId('vh');
24
+ expect(el.id).toBe('vh-1');
25
+ expect(el).toHaveClass('vh');
26
+ });
27
+ });