@polderlabs/bizar 6.3.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) 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/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-CW6fWKds.js} +3 -3
  5. package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js.map → EnvVarsSection-CW6fWKds.js.map} +1 -1
  6. package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +1 -0
  7. package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-D7zRhU9K.js.map} +1 -1
  8. package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +1 -0
  9. package/bizar-dash/dist/assets/{MobileSettings-CEQNJNLJ.js.map → MobileSettings-K-sNsn_r.js.map} +1 -1
  10. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
  11. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
  12. package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
  13. package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
  14. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
  15. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
  16. package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
  17. package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
  18. package/bizar-dash/dist/assets/main-C-Md3DaY.css +1 -0
  19. package/bizar-dash/dist/assets/main-CO8fIxQK.js +18 -0
  20. package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +1 -0
  21. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
  22. package/bizar-dash/dist/assets/{markdown-tOLaD6nm.js.map → markdown-w2bDDkAH.js.map} +1 -1
  23. package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +1 -0
  24. package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DXYCA0hC.js.map} +1 -1
  25. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +2 -0
  26. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +1 -0
  27. package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
  28. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
  29. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
  30. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +2 -0
  31. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +1 -0
  32. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
  33. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
  34. package/bizar-dash/dist/index.html +12 -10
  35. package/bizar-dash/dist/mobile.html +7 -4
  36. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  37. package/bizar-dash/src/server/api.mjs +11 -0
  38. package/bizar-dash/src/server/memory-consolidator.mjs +218 -0
  39. package/bizar-dash/src/server/memory-distillation.mjs +296 -0
  40. package/bizar-dash/src/server/routes/distill.mjs +129 -0
  41. package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
  42. package/bizar-dash/src/web/App.tsx +4 -0
  43. package/bizar-dash/src/web/components/Tag.tsx +18 -5
  44. package/bizar-dash/src/web/components/Topbar.tsx +3 -0
  45. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
  46. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
  47. package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
  48. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
  49. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
  50. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
  51. package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
  52. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
  53. package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
  54. package/bizar-dash/src/web/lib/types.ts +25 -0
  55. package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
  56. package/bizar-dash/src/web/styles/main.css +251 -0
  57. package/bizar-dash/src/web/styles/tasks-redesign.css +231 -0
  58. package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
  59. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
  60. package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
  61. package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
  62. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
  63. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
  64. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
  65. package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
  66. package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
  67. package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
  68. package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
  69. package/bizar-dash/src/web/ui/controls/controls.css +454 -0
  70. package/bizar-dash/src/web/ui/controls/index.ts +33 -0
  71. package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
  72. package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
  73. package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
  74. package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
  75. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
  76. package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
  77. package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
  78. package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
  79. package/bizar-dash/src/web/ui/data/data.css +535 -0
  80. package/bizar-dash/src/web/ui/data/index.ts +31 -0
  81. package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
  82. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
  83. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
  84. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
  85. package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
  86. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
  87. package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
  88. package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
  89. package/bizar-dash/src/web/ui/index.ts +56 -0
  90. package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
  91. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
  92. package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
  93. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
  94. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
  95. package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
  96. package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
  97. package/bizar-dash/src/web/ui/layout/index.ts +33 -0
  98. package/bizar-dash/src/web/ui/layout/layout.css +413 -0
  99. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
  100. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
  101. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
  102. package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
  103. package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
  104. package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
  105. package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
  106. package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
  107. package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
  108. package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
  109. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
  110. package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
  111. package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
  112. package/bizar-dash/src/web/ui/styles/globals.css +86 -0
  113. package/bizar-dash/src/web/ui/styles/reset.css +125 -0
  114. package/bizar-dash/src/web/ui/styles/tokens.css +195 -0
  115. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
  116. package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
  117. package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
  118. package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
  119. package/bizar-dash/src/web/views/Agents.tsx +506 -266
  120. package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
  121. package/bizar-dash/src/web/views/Overview.tsx +996 -448
  122. package/bizar-dash/src/web/views/Tasks.tsx +277 -273
  123. package/bizar-dash/tests/communication-log.test.tsx +67 -0
  124. package/bizar-dash/tests/dep-graph.test.tsx +40 -0
  125. package/bizar-dash/tests/event-log.test.tsx +61 -0
  126. package/bizar-dash/tests/goal-input.test.tsx +72 -0
  127. package/bizar-dash/tests/goap-planner.test.ts +68 -0
  128. package/bizar-dash/tests/quality-gates.test.tsx +59 -0
  129. package/bizar-dash/tests/setup.ts +31 -1
  130. package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
  131. package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
  132. package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
  133. package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
  134. package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
  135. package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
  136. package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
  137. package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
  138. package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
  139. package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
  140. package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
  141. package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
  142. package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
  143. package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
  144. package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
  145. package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
  146. package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
  147. package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
  148. package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
  149. package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
  150. package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
  151. package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
  152. package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
  153. package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
  154. package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
  155. package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
  156. package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
  157. package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
  158. package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
  159. package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
  160. package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
  161. package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
  162. package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
  163. package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
  164. package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
  165. package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
  166. package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
  167. package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
  168. package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
  169. package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
  170. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
  171. package/bizar-dash/tests/views/Agents.test.tsx +359 -0
  172. package/bizar-dash/tests/views/Overview.test.tsx +413 -0
  173. package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
  174. package/bizar-dash/vitest.config.ts +3 -1
  175. package/cli/__tests__/cost-gate.test.mjs +301 -0
  176. package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
  177. package/cli/bin.mjs +38 -0
  178. package/cli/commands/claim.mjs +282 -0
  179. package/cli/commands/cost.mjs +279 -0
  180. package/cli/cost-gate.mjs +501 -0
  181. package/cli/feature-list-bridge.mjs +400 -0
  182. package/cli/memory-constants.mjs +17 -0
  183. package/cli/worker-dispatcher.mjs +259 -0
  184. package/cli/worker-dispatcher.test.mjs +204 -0
  185. package/config/trigger-patterns.json +186 -0
  186. package/package.json +2 -2
  187. package/packages/sdk/package.json +1 -1
  188. package/packages/sdk/src/agent-registry.ts +304 -0
  189. package/packages/sdk/src/consensus/byzantine.ts +587 -0
  190. package/packages/sdk/src/consensus/index.ts +147 -0
  191. package/packages/sdk/src/consensus/queen.ts +173 -0
  192. package/packages/sdk/src/consensus/types.ts +199 -0
  193. package/packages/sdk/src/federation/audit.ts +156 -0
  194. package/packages/sdk/src/federation/budget.ts +269 -0
  195. package/packages/sdk/src/federation/envelope.ts +144 -0
  196. package/packages/sdk/src/federation/hmac.ts +160 -0
  197. package/packages/sdk/src/federation/index.ts +443 -0
  198. package/packages/sdk/src/federation/pii.ts +241 -0
  199. package/packages/sdk/src/federation/policy.ts +112 -0
  200. package/packages/sdk/src/federation/trust.ts +146 -0
  201. package/packages/sdk/src/index.ts +67 -0
  202. package/packages/sdk/src/mcp/server.ts +351 -1
  203. package/packages/sdk/src/router/codemod-intent.ts +125 -0
  204. package/packages/sdk/src/router/index.ts +247 -0
  205. package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
  206. package/packages/sdk/src/router/memory-distillation.ts +249 -0
  207. package/packages/sdk/src/router/model-router.ts +235 -0
  208. package/packages/sdk/src/router/q-learning-router.ts +304 -0
  209. package/packages/sdk/src/swarm-topology.ts +279 -0
  210. package/packages/sdk/tests/agent-registry.test.ts +285 -0
  211. package/packages/sdk/tests/consensus.test.ts +376 -0
  212. package/packages/sdk/tests/federation/audit.test.ts +164 -0
  213. package/packages/sdk/tests/federation/budget.test.ts +189 -0
  214. package/packages/sdk/tests/federation/envelope.test.ts +120 -0
  215. package/packages/sdk/tests/federation/hmac.test.ts +194 -0
  216. package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
  217. package/packages/sdk/tests/federation/pii.test.ts +106 -0
  218. package/packages/sdk/tests/federation/policy.test.ts +140 -0
  219. package/packages/sdk/tests/federation/trust.test.ts +124 -0
  220. package/packages/sdk/tests/mcp-tools.test.ts +204 -0
  221. package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
  222. package/packages/sdk/tests/model-router.test.mjs +108 -0
  223. package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
  224. package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
  225. package/packages/sdk/tests/router.test.mjs +91 -0
  226. package/packages/sdk/tests/sdk.test.mjs +26 -2
  227. package/packages/sdk/tests/swarm-topology.test.ts +258 -0
  228. package/scripts/bh-full-e2e.mjs +11 -15
  229. package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
  230. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
  231. package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
  232. package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
  233. package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
  234. package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
  235. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
  236. package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
  237. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
  238. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
  239. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
  240. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
  241. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
  242. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
  243. package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
  244. package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
  245. package/packages/sdk/package-lock.json +0 -32
@@ -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
+ });
@@ -0,0 +1,359 @@
1
+ // tests/views/Agents.test.tsx — Wave 3 Agents view (split-pane roster + detail panel).
2
+ //
3
+ // Covers the redesign contract: DataTable renders rows from snapshot, clicking
4
+ // a row selects an agent and updates the detail panel, category + search
5
+ // filters narrow the table, and the Edit button fires the handler. The
6
+ // create/delete/invoke modals use the legacy Modal and are exercised here
7
+ // only at the click-button level — their form contents are unchanged from
8
+ // the previous view and live in the legacy test suite.
9
+
10
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
11
+ import { render, screen, waitFor, within } from '@testing-library/react';
12
+ import userEvent from '@testing-library/user-event';
13
+ import React from 'react';
14
+ import { ToastProvider } from '../../src/web/components/Toast';
15
+ import { ModalProvider } from '../../src/web/components/Modal';
16
+ import { Agents } from '../../src/web/views/Agents';
17
+ import type { Agent, Settings, Snapshot } from '../../src/web/lib/types';
18
+
19
+ // ─── API mock ────────────────────────────────────────────────────────────
20
+
21
+ const apiGet = vi.fn();
22
+ const apiPost = vi.fn();
23
+ const apiPut = vi.fn();
24
+ const apiDel = vi.fn();
25
+
26
+ vi.mock('../../src/web/lib/api', () => ({
27
+ api: {
28
+ get: (...args: unknown[]) => apiGet(...args),
29
+ post: (...args: unknown[]) => apiPost(...args),
30
+ put: (...args: unknown[]) => apiPut(...args),
31
+ del: (...args: unknown[]) => apiDel(...args),
32
+ },
33
+ }));
34
+
35
+ // The Agents view does not own a WS subscription (parent App.tsx does), but
36
+ // stubbing Ws makes the file load-safe if a future change introduces one.
37
+ vi.mock('../../src/web/lib/ws', () => ({
38
+ Ws: class {
39
+ on() { return () => undefined; }
40
+ onStatus() { return () => undefined; }
41
+ close() {}
42
+ },
43
+ }));
44
+
45
+ // ─── Test fixtures ───────────────────────────────────────────────────────
46
+
47
+ const odin: Agent = {
48
+ name: 'odin',
49
+ description: 'Top-tier orchestrator',
50
+ model: 'anthropic/claude-3-5-sonnet',
51
+ mode: 'primary',
52
+ file: 'odin.md',
53
+ path: '/agents/odin.md',
54
+ mtime: Date.now() - 3600_000,
55
+ tools: ['bash', 'read', 'edit'],
56
+ tags: ['orchestrator', 'reasoning'],
57
+ category: 'reasoning',
58
+ color: '#8b5cf6',
59
+ status: 'working',
60
+ isStuck: false,
61
+ };
62
+
63
+ const frigg: Agent = {
64
+ name: 'frigg',
65
+ description: 'Quality + memory curator',
66
+ model: 'anthropic/claude-3-5-haiku',
67
+ mode: 'subagent',
68
+ file: 'frigg.md',
69
+ path: '/agents/frigg.md',
70
+ mtime: Date.now() - 7200_000,
71
+ tools: ['read', 'grep'],
72
+ tags: ['curator', 'analysis'],
73
+ category: 'analysis',
74
+ status: 'idle',
75
+ };
76
+
77
+ const tyr: Agent = {
78
+ name: 'tyr',
79
+ description: 'Implementation engine',
80
+ model: 'openai/gpt-4o',
81
+ mode: 'subagent',
82
+ file: 'tyr.md',
83
+ path: '/agents/tyr.md',
84
+ mtime: Date.now() - 1800_000,
85
+ tools: ['bash', 'edit', 'write'],
86
+ tags: ['implementation', 'code'],
87
+ category: 'code',
88
+ color: '#2563eb',
89
+ status: 'working',
90
+ isStuck: true,
91
+ };
92
+
93
+ function makeSnapshot(agents: Agent[] = [odin, frigg, tyr]): Snapshot {
94
+ return {
95
+ agents,
96
+ } as unknown as Snapshot;
97
+ }
98
+
99
+ const dummySettings = {} as Settings;
100
+
101
+ const Harness: React.FC<{ children: React.ReactNode }> = ({ children }) => (
102
+ <ToastProvider>
103
+ <ModalProvider>{children}</ModalProvider>
104
+ </ToastProvider>
105
+ );
106
+
107
+ // ─── Tests ───────────────────────────────────────────────────────────────
108
+
109
+ describe('Agents view (Wave 3 redesign)', () => {
110
+ beforeEach(() => {
111
+ apiGet.mockReset();
112
+ apiPost.mockReset();
113
+ apiPut.mockReset();
114
+ apiDel.mockReset();
115
+ // Default mock for any GET that may sneak through (modal loads).
116
+ apiGet.mockImplementation(async () => ({}));
117
+ apiPost.mockResolvedValue({});
118
+ apiPut.mockResolvedValue({});
119
+ apiDel.mockResolvedValue({});
120
+ // confirm() always true for delete tests
121
+ vi.spyOn(window, 'confirm').mockReturnValue(true);
122
+ });
123
+
124
+ it('renders the DataTable rows from the snapshot', async () => {
125
+ render(
126
+ <Harness>
127
+ <Agents
128
+ snapshot={makeSnapshot()}
129
+ settings={dummySettings}
130
+ activeTab="agents"
131
+ setActiveTab={() => undefined}
132
+ refreshSnapshot={async () => undefined}
133
+ />
134
+ </Harness>,
135
+ );
136
+
137
+ expect(await screen.findByTestId('agents-table')).toBeInTheDocument();
138
+ expect(screen.getByTestId('agents-count').textContent).toContain('(3)');
139
+
140
+ const table = screen.getByTestId('agents-table');
141
+ expect(within(table).getByText('odin')).toBeInTheDocument();
142
+ expect(within(table).getByText('frigg')).toBeInTheDocument();
143
+ expect(within(table).getByText('tyr')).toBeInTheDocument();
144
+ });
145
+
146
+ it('shows the empty state when no agents match', async () => {
147
+ render(
148
+ <Harness>
149
+ <Agents
150
+ snapshot={makeSnapshot([])}
151
+ settings={dummySettings}
152
+ activeTab="agents"
153
+ setActiveTab={() => undefined}
154
+ refreshSnapshot={async () => undefined}
155
+ />
156
+ </Harness>,
157
+ );
158
+
159
+ expect(await screen.findByText(/No agents found/i)).toBeInTheDocument();
160
+ expect(screen.getByTestId('agents-count').textContent).toContain('(0)');
161
+ });
162
+
163
+ it('clicking a row populates the detail panel', async () => {
164
+ const user = userEvent.setup();
165
+ render(
166
+ <Harness>
167
+ <Agents
168
+ snapshot={makeSnapshot()}
169
+ settings={dummySettings}
170
+ activeTab="agents"
171
+ setActiveTab={() => undefined}
172
+ refreshSnapshot={async () => undefined}
173
+ />
174
+ </Harness>,
175
+ );
176
+
177
+ const table = await screen.findByTestId('agents-table');
178
+ await user.click(within(table).getByText('frigg'));
179
+
180
+ // Detail panel shows the agent's metadata — use unique marker
181
+ await waitFor(() => {
182
+ expect(screen.getByText('Quality + memory curator')).toBeInTheDocument();
183
+ });
184
+ // Frigg-specific tag (curator) appears in the detail panel
185
+ expect(screen.getAllByText('curator').length).toBeGreaterThan(0);
186
+ // Edit button is rendered with frigg-specific testid
187
+ expect(screen.getByTestId('agents-detail-edit-frigg')).toBeInTheDocument();
188
+ // Frigg's path shows in the panel description
189
+ expect(screen.getByText('/agents/frigg.md')).toBeInTheDocument();
190
+ });
191
+
192
+ it('category filter narrows the visible agents', async () => {
193
+ const user = userEvent.setup();
194
+ render(
195
+ <Harness>
196
+ <Agents
197
+ snapshot={makeSnapshot()}
198
+ settings={dummySettings}
199
+ activeTab="agents"
200
+ setActiveTab={() => undefined}
201
+ refreshSnapshot={async () => undefined}
202
+ />
203
+ </Harness>,
204
+ );
205
+
206
+ const table = await screen.findByTestId('agents-table');
207
+ expect(within(table).getByText('tyr')).toBeInTheDocument();
208
+
209
+ const filter = screen.getByTestId('agents-category-filter') as HTMLSelectElement;
210
+ await user.selectOptions(filter, 'code');
211
+
212
+ expect(within(table).queryByText('odin')).not.toBeInTheDocument();
213
+ expect(within(table).queryByText('frigg')).not.toBeInTheDocument();
214
+ expect(within(table).getByText('tyr')).toBeInTheDocument();
215
+ expect(screen.getByTestId('agents-count').textContent).toContain('(1)');
216
+ });
217
+
218
+ it('search filters by name / description / tag', async () => {
219
+ const user = userEvent.setup();
220
+ render(
221
+ <Harness>
222
+ <Agents
223
+ snapshot={makeSnapshot()}
224
+ settings={dummySettings}
225
+ activeTab="agents"
226
+ setActiveTab={() => undefined}
227
+ refreshSnapshot={async () => undefined}
228
+ />
229
+ </Harness>,
230
+ );
231
+
232
+ const table = await screen.findByTestId('agents-table');
233
+ const search = screen.getByTestId('agents-search');
234
+
235
+ // Tag match
236
+ await user.type(search, 'curator');
237
+ expect(within(table).queryByText('odin')).not.toBeInTheDocument();
238
+ expect(within(table).queryByText('frigg')).toBeInTheDocument();
239
+ expect(within(table).queryByText('tyr')).not.toBeInTheDocument();
240
+
241
+ // Description match
242
+ await user.clear(search);
243
+ await user.type(search, 'Implementation');
244
+ expect(within(table).getByText('tyr')).toBeInTheDocument();
245
+ expect(within(table).queryByText('frigg')).not.toBeInTheDocument();
246
+
247
+ // Name match (case-insensitive)
248
+ await user.clear(search);
249
+ await user.type(search, 'ODIN');
250
+ expect(within(table).getByText('odin')).toBeInTheDocument();
251
+ expect(within(table).queryByText('tyr')).not.toBeInTheDocument();
252
+ });
253
+
254
+ it('Edit button on the detail panel loads the agent and opens the edit modal', async () => {
255
+ const user = userEvent.setup();
256
+ apiGet.mockImplementation(async (path: string) => {
257
+ if (path === '/agents/odin') {
258
+ return { ...odin, prompt: 'You are Odin.' };
259
+ }
260
+ return {};
261
+ });
262
+
263
+ render(
264
+ <Harness>
265
+ <Agents
266
+ snapshot={makeSnapshot()}
267
+ settings={dummySettings}
268
+ activeTab="agents"
269
+ setActiveTab={() => undefined}
270
+ refreshSnapshot={async () => undefined}
271
+ />
272
+ </Harness>,
273
+ );
274
+
275
+ const table = await screen.findByTestId('agents-table');
276
+ await user.click(within(table).getByText('odin'));
277
+
278
+ const editBtn = await screen.findByTestId('agents-detail-edit-odin');
279
+ await user.click(editBtn);
280
+
281
+ await waitFor(() => {
282
+ expect(apiGet).toHaveBeenCalledWith('/agents/odin');
283
+ });
284
+
285
+ // Edit modal renders with the existing prompt pre-filled.
286
+ await waitFor(() => {
287
+ const promptArea = screen.getByLabelText(/system prompt/i) as HTMLTextAreaElement;
288
+ expect(promptArea.value).toBe('You are Odin.');
289
+ });
290
+ expect(screen.getByText('Edit odin')).toBeInTheDocument();
291
+ });
292
+
293
+ it('pulse fires on StatusDot when agent is stuck', async () => {
294
+ render(
295
+ <Harness>
296
+ <Agents
297
+ snapshot={makeSnapshot()}
298
+ settings={dummySettings}
299
+ activeTab="agents"
300
+ setActiveTab={() => undefined}
301
+ refreshSnapshot={async () => undefined}
302
+ />
303
+ </Harness>,
304
+ );
305
+
306
+ const table = await screen.findByTestId('agents-table');
307
+ // Tyr is stuck — find the row and confirm the pulsing status dot is present.
308
+ const tyrRow = within(table).getByText('tyr').closest('tr');
309
+ expect(tyrRow).not.toBeNull();
310
+ const pulseDot = tyrRow!.querySelector('.bizar-status-dot--pulse');
311
+ expect(pulseDot).not.toBeNull();
312
+ });
313
+
314
+ it('renders the "No agent selected" placeholder when nothing is picked', async () => {
315
+ render(
316
+ <Harness>
317
+ <Agents
318
+ snapshot={makeSnapshot()}
319
+ settings={dummySettings}
320
+ activeTab="agents"
321
+ setActiveTab={() => undefined}
322
+ refreshSnapshot={async () => undefined}
323
+ />
324
+ </Harness>,
325
+ );
326
+
327
+ expect(await screen.findByText(/No agent selected/i)).toBeInTheDocument();
328
+ });
329
+
330
+ it('refresh button reloads /agents and updates count', async () => {
331
+ const user = userEvent.setup();
332
+ apiGet.mockImplementation(async (path: string) => {
333
+ if (path === '/agents') return { agents: [odin] };
334
+ return {};
335
+ });
336
+
337
+ render(
338
+ <Harness>
339
+ <Agents
340
+ snapshot={makeSnapshot()}
341
+ settings={dummySettings}
342
+ activeTab="agents"
343
+ setActiveTab={() => undefined}
344
+ refreshSnapshot={async () => undefined}
345
+ />
346
+ </Harness>,
347
+ );
348
+
349
+ expect(screen.getByTestId('agents-count').textContent).toContain('(3)');
350
+ await user.click(screen.getByTestId('agents-refresh'));
351
+
352
+ await waitFor(() => {
353
+ expect(apiGet).toHaveBeenCalledWith('/agents');
354
+ });
355
+ await waitFor(() => {
356
+ expect(screen.getByTestId('agents-count').textContent).toContain('(1)');
357
+ });
358
+ });
359
+ });