@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
@@ -1,55 +1,73 @@
1
- // src/views/Tasks.tsx
2
- //
3
- // v4.6.0 — Simplified task board.
4
- //
5
- // Design goals (per Odin):
6
- // - Create flow = title + description (+ optional priority hint + tags).
7
- // No agent picker. Odin decides what to do with the task and which
8
- // agent picks it up; the UI just records intent.
9
- // - Tasks grouped by status into 5 columns:
10
- // Todo (api: queued)
11
- // In progress (api: doing)
12
- // Done (api: done)
13
- // Failed (api: blocked)
14
- // Backlog (api: backlog, parked ideas)
15
- // - Each task row shows title, status, priority, createdAt, and the
16
- // agent that auto-claimed it (read-only — set by the orchestrator).
17
- // - Per-task actions: edit, delete, retry, change status.
18
- // - Backlog view: parked ideas; one-click "promote to Todo".
19
- //
20
- // We keep the API contract (statuses: queued/doing/done/blocked/backlog)
21
- // so the dashboard doesn't break the existing server, tasks store, and
22
- // tests. The UI maps API statuses to friendlier labels.
1
+ /*
2
+ * Tasks.tsx — Wave 3 redesigned kanban board (Supabase-style simplicity).
3
+ *
4
+ * The view groups tasks into 5 columns by API status (queued | doing | done
5
+ * | blocked | backlog) and renders each column as a `Panel` from the new
6
+ * design system. Task rows are compact (~76px) cards with a left-edge
7
+ * priority accent (3px) derived from the priority token. The redesign
8
+ * intentionally uses only design-system primitives (Panel, Grid, Stack,
9
+ * Inline, Badge, StatusDot, Button, IconButton, SearchInput, Select,
10
+ * EmptyState, LoadingState) — no legacy `.task-card` / `.kanban-column`
11
+ * classes from main.css.
12
+ *
13
+ * Hard constraints preserved from the previous implementation:
14
+ * - Same export name (`Tasks`) and same `Props` shape so App.tsx keeps
15
+ * importing without changes.
16
+ * - Same API contracts (`api.get('/tasks')`, `api.patch(.../status)`, etc).
17
+ * - Same WS subscriptions (`tasks:change`, `tasks:delete`).
18
+ * - Same COLUMNS array — the API status enum is the public contract.
19
+ * - BacklogPanel is still imported from `components/tasks/BacklogPanel`
20
+ * and receives the same props (out-of-scope for this redesign).
21
+ * - Legacy `Modal` is reused for the create/edit dialogs (out-of-scope).
22
+ * - 30s tick for relative-time labels.
23
+ *
24
+ * What changed:
25
+ * - Columns are now `Panel`s with a StatusDot + count in the header.
26
+ * - Task rows render with `Badge` for status/priority/agent and
27
+ * `IconButton` for actions (edit, submit-to-odin, retry, delete).
28
+ * - The whole task row is clickable (delegates to the edit modal) —
29
+ * action buttons stop propagation so they keep their dedicated path.
30
+ * - Toolbar uses `SearchInput` + `Select` (priority) instead of raw
31
+ * inputs.
32
+ */
23
33
  import React, { useEffect, useMemo, useState } from 'react';
24
34
  import {
25
35
  CheckSquare,
26
36
  Plus,
27
37
  Trash2,
28
- ChevronLeft,
29
- ChevronRight,
30
- Search,
31
- X as XIcon,
32
- Clock,
38
+ RefreshCw,
33
39
  Tag as TagIcon,
34
40
  ArchiveRestore,
35
- RefreshCw,
41
+ RotateCw,
36
42
  Send,
37
43
  Inbox,
38
- Sparkles,
39
44
  Edit2,
40
- RotateCw,
41
45
  Bot,
42
46
  } from 'lucide-react';
43
47
  import { Button } from '../components/Button';
44
48
  import { Card, CardTitle, CardMeta } from '../components/Card';
45
- import { Spinner } from '../components/Spinner';
46
- import { Tag } from '../components/Tag';
47
49
  import { useModal } from '../components/Modal';
48
50
  import { useToast } from '../components/Toast';
49
51
  import { api, enhancePrompt } from '../lib/api';
50
- import { cn, formatRelative, priorityColors } from '../lib/utils';
52
+ import { formatRelative } from '../lib/utils';
51
53
  import type { Settings, Snapshot, Task } from '../lib/types';
52
54
  import { BacklogPanel } from '../components/tasks/BacklogPanel';
55
+ import {
56
+ Badge,
57
+ cx,
58
+ Grid,
59
+ IconButton,
60
+ Inline,
61
+ LoadingState,
62
+ Panel,
63
+ SearchInput,
64
+ Select,
65
+ Stack,
66
+ StatusDot,
67
+ type BadgeVariant,
68
+ } from '../ui';
69
+
70
+ import '../styles/tasks-redesign.css';
53
71
 
54
72
  type Props = {
55
73
  snapshot: Snapshot;
@@ -60,6 +78,7 @@ type Props = {
60
78
  };
61
79
 
62
80
  type StatusKind = 'info' | 'accent' | 'success' | 'warning' | 'error';
81
+ type PriorityKind = 'low' | 'normal' | 'high';
63
82
 
64
83
  /**
65
84
  * UI <-> API status mapping. We keep the existing API enum
@@ -67,17 +86,49 @@ type StatusKind = 'info' | 'accent' | 'success' | 'warning' | 'error';
67
86
  * rename the labels here so the dashboard, server, and tests stay in
68
87
  * sync. Switch back if you need to expose the raw API value.
69
88
  */
70
- const COLUMNS: { id: Task['status']; label: string; kind: StatusKind }[] = [
71
- { id: 'queued', label: 'Todo', kind: 'info' },
72
- { id: 'doing', label: 'In progress', kind: 'accent' },
73
- { id: 'done', label: 'Done', kind: 'success' },
74
- { id: 'blocked', label: 'Failed', kind: 'error' },
89
+ const COLUMNS: {
90
+ id: Task['status'];
91
+ label: string;
92
+ kind: StatusKind;
93
+ dotVariant: 'info' | 'success' | 'warning' | 'danger';
94
+ }[] = [
95
+ { id: 'queued', label: 'Todo', kind: 'info', dotVariant: 'info' },
96
+ { id: 'doing', label: 'In progress', kind: 'accent', dotVariant: 'info' },
97
+ { id: 'done', label: 'Done', kind: 'success', dotVariant: 'success' },
98
+ { id: 'blocked', label: 'Failed', kind: 'error', dotVariant: 'danger' },
99
+ ];
100
+
101
+ const PRIORITIES: readonly PriorityKind[] = ['low', 'normal', 'high'];
102
+
103
+ const PRIORITY_OPTIONS = [
104
+ { value: '', label: 'All' },
105
+ ...PRIORITIES.map((p) => ({ value: p, label: p })),
75
106
  ];
76
107
 
77
- const PRIORITIES = ['low', 'normal', 'high'] as const;
78
- type Priority = (typeof PRIORITIES)[number];
108
+ const STATUS_BADGE: Record<StatusKind, BadgeVariant> = {
109
+ info: 'info',
110
+ accent: 'accent',
111
+ success: 'success',
112
+ warning: 'warning',
113
+ error: 'danger',
114
+ };
115
+
116
+ const PRIORITY_BADGE: Record<PriorityKind, BadgeVariant> = {
117
+ high: 'danger',
118
+ normal: 'info',
119
+ low: 'neutral',
120
+ };
79
121
 
80
- function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
122
+ const PRIORITY_ROW_CLASS: Record<PriorityKind, string> = {
123
+ high: 'tasks-wave3__row--priority-high',
124
+ normal: 'tasks-wave3__row--priority-normal',
125
+ low: 'tasks-wave3__row--priority-low',
126
+ };
127
+
128
+ function TasksInner({
129
+ snapshot,
130
+ refreshSnapshot,
131
+ }: Props) {
81
132
  const toast = useToast();
82
133
  const modal = useModal();
83
134
  const [tasks, setTasks] = useState<Task[]>(snapshot.tasks || []);
@@ -219,72 +270,63 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
219
270
  }
220
271
  };
221
272
 
273
+ const handleTaskClick = (task: Task) => {
274
+ openEditTaskModal(modal, toast, task, setTasks, reload, refreshSnapshot);
275
+ };
276
+
222
277
  return (
223
- <div className="view view-tasks">
278
+ <div className="view view-tasks tasks-wave3">
224
279
  {/* Hidden live region for screen-reader announcements of status changes. */}
225
280
  <div className="sr-only" role="status" aria-live="polite">
226
281
  {statusAnnouncement}
227
282
  </div>
228
- <header className="view-header">
229
- <div className="view-header-text">
230
- <h2 className="view-title">
231
- <CheckSquare size={18} /> Tasks ({sorted.length})
283
+ <header className="tasks-wave3__header">
284
+ <div>
285
+ <h2 className="tasks-wave3__title-row">
286
+ <CheckSquare size={18} aria-hidden /> Tasks ({sorted.length})
232
287
  </h2>
233
- <p className="view-subtitle">
288
+ <p className="tasks-wave3__subtitle">
234
289
  Add a task — title and description is all you need. Odin picks the agent and priority.
235
290
  </p>
236
291
  </div>
237
292
  </header>
238
293
 
239
- {/* Toolbar: search, filter, sort, actions */}
240
- <div className="tasks-toolbar">
241
- <div className="tasks-toolbar-group">
242
- <div className="search-input" style={{ width: 200 }}>
243
- <Search size={12} aria-hidden />
244
- <input
245
- className="input"
246
- type="text"
247
- placeholder="Search…"
248
- value={filter}
249
- onChange={(e) => setFilter(e.target.value)}
250
- aria-label="Search tasks"
251
- />
252
- {filter && (
253
- <button
254
- type="button"
255
- className="icon-btn"
256
- aria-label="Clear search"
257
- onClick={() => setFilter('')}
258
- >
259
- <XIcon size={12} />
260
- </button>
261
- )}
262
- </div>
294
+ {/* Toolbar: search, priority filter, refresh, backlog toggle, new task */}
295
+ <div className="tasks-wave3__toolbar" role="toolbar" aria-label="Task filters">
296
+ <div className="tasks-wave3__toolbar-group">
297
+ <SearchInput
298
+ inputSize="sm"
299
+ value={filter}
300
+ onChange={((v: unknown) => setFilter(String(v))) as unknown as React.ComponentProps<typeof SearchInput>['onChange']}
301
+ placeholder="Search…"
302
+ aria-label="Search tasks"
303
+ />
263
304
  </div>
264
305
 
265
- <div className="tasks-toolbar-divider" />
266
-
267
- <div className="tasks-toolbar-group">
268
- <label className="tasks-toolbar-label" htmlFor="tasks-priority-filter">Priority</label>
269
- <select
270
- id="tasks-priority-filter"
271
- className="select select-sm"
306
+ <div className="tasks-wave3__toolbar-group">
307
+ <span className="tasks-wave3__toolbar-label" id="tasks-priority-filter-label">
308
+ Priority
309
+ </span>
310
+ <Select
311
+ inputSize="sm"
312
+ aria-labelledby="tasks-priority-filter-label"
313
+ options={PRIORITY_OPTIONS}
272
314
  value={priorityFilter}
273
- onChange={(e) => setPriorityFilter(e.target.value)}
274
- title="Filter by priority"
275
- >
276
- <option value="">All</option>
277
- {PRIORITIES.map((p) => (
278
- <option key={p} value={p}>{p}</option>
279
- ))}
280
- </select>
315
+ onChange={(e) => setPriorityFilter((e.target as HTMLSelectElement).value)}
316
+ />
281
317
  </div>
282
318
 
283
- <div className="tasks-toolbar-spacer" />
319
+ <div className="tasks-wave3__toolbar-spacer" />
284
320
 
285
- <div className="tasks-toolbar-group">
286
- <Button variant="ghost" size="sm" onClick={reload} title="Refresh" aria-label="Refresh tasks">
287
- <RefreshCw size={14} />
321
+ <div className="tasks-wave3__toolbar-group">
322
+ <Button
323
+ variant="ghost"
324
+ size="sm"
325
+ onClick={reload}
326
+ title="Refresh"
327
+ aria-label="Refresh tasks"
328
+ >
329
+ <RefreshCw size={14} aria-hidden />
288
330
  </Button>
289
331
  {backlogCount > 0 && (
290
332
  <Button
@@ -293,9 +335,11 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
293
335
  onClick={() => setShowBacklog((v) => !v)}
294
336
  title={showBacklog ? 'Hide backlog' : 'Show backlog'}
295
337
  >
296
- <Inbox size={14} />
338
+ <Inbox size={14} aria-hidden />
297
339
  Backlog
298
- <span className="badge">{backlogCount}</span>
340
+ <span className="tasks-wave3__column-count" aria-label={`${backlogCount} items`}>
341
+ {backlogCount}
342
+ </span>
299
343
  </Button>
300
344
  )}
301
345
  <Button
@@ -303,7 +347,7 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
303
347
  size="sm"
304
348
  onClick={() => openCreateTaskModal(modal, toast, setTasks, reload, refreshSnapshot)}
305
349
  >
306
- <Plus size={14} /> New task
350
+ <Plus size={14} aria-hidden /> New task
307
351
  </Button>
308
352
  </div>
309
353
  </div>
@@ -316,218 +360,167 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
316
360
  )}
317
361
 
318
362
  {loading ? (
319
- <div className="view-loading"><Spinner size="lg" /></div>
363
+ <LoadingState label="Loading tasks…" />
320
364
  ) : (
321
- <div className="kanban">
322
- {COLUMNS.map((col) => (
323
- <KanbanColumn
324
- key={col.id}
325
- column={col}
326
- tasks={sorted.filter((t) => t.status === col.id)}
327
- onMove={moveTask}
328
- onDelete={deleteTask}
329
- onRetry={retryTask}
330
- onEdit={(t) => openEditTaskModal(modal, toast, t, setTasks, reload, refreshSnapshot)}
331
- onSubmitToOdin={submitToOdin}
332
- tick={tick}
333
- />
334
- ))}
335
- </div>
365
+ <Grid cols={5} gap={3} className="tasks-wave3__kanban">
366
+ {COLUMNS.map((col) => {
367
+ const columnTasks = sorted.filter((t) => t.status === col.id);
368
+ return (
369
+ <Panel
370
+ key={col.id}
371
+ padding={3}
372
+ className="tasks-wave3__column"
373
+ title={
374
+ <span className="tasks-wave3__column-title">
375
+ <StatusDot variant={col.dotVariant} size="sm" label={col.label} />
376
+ {col.label}
377
+ </span>
378
+ }
379
+ actions={
380
+ <span
381
+ className="tasks-wave3__column-count"
382
+ aria-label={`${columnTasks.length} tasks`}
383
+ >
384
+ {columnTasks.length}
385
+ </span>
386
+ }
387
+ >
388
+ <Stack gap={2} className="tasks-wave3__column-body">
389
+ {columnTasks.length === 0 ? (
390
+ <div className="tasks-wave3__column-empty">No tasks</div>
391
+ ) : (
392
+ columnTasks.map((t) => (
393
+ <TaskRow
394
+ key={t.id}
395
+ task={t}
396
+ column={col}
397
+ onTaskClick={handleTaskClick}
398
+ onEdit={() => openEditTaskModal(modal, toast, t, setTasks, reload, refreshSnapshot)}
399
+ onDelete={() => deleteTask(t.id)}
400
+ onRetry={() => retryTask(t.id)}
401
+ onSubmitToOdin={() => submitToOdin(t.id)}
402
+ tick={tick}
403
+ />
404
+ ))
405
+ )}
406
+ </Stack>
407
+ </Panel>
408
+ );
409
+ })}
410
+ </Grid>
336
411
  )}
337
412
  </div>
338
413
  );
339
414
  }
340
415
 
341
- // ─── Column ────────────────────────────────────────────────────────────────
416
+ // ─── Row ─────────────────────────────────────────────────────────────────
342
417
 
343
- function KanbanColumn({
344
- column,
345
- tasks,
346
- onMove,
347
- onDelete,
348
- onRetry,
349
- onEdit,
350
- onSubmitToOdin,
351
- tick,
352
- }: {
353
- column: { id: string; label: string; kind: StatusKind };
354
- tasks: Task[];
355
- onMove: (id: string, status: string) => void;
356
- onDelete: (id: string) => void;
357
- onRetry: (id: string) => void;
358
- onEdit: (t: Task) => void;
359
- onSubmitToOdin: (id: string) => void;
418
+ type RowProps = {
419
+ task: Task;
420
+ column: (typeof COLUMNS)[number];
421
+ onTaskClick: (task: Task) => void;
422
+ onEdit: () => void;
423
+ onDelete: () => void;
424
+ onRetry: () => void;
425
+ onSubmitToOdin: () => void;
360
426
  tick: number;
361
- }) {
362
- const [dragOver, setDragOver] = useState(false);
363
- return (
364
- <div
365
- className={cn('kanban-column', dragOver && 'kanban-column-drop')}
366
- data-column={column.id}
367
- onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
368
- onDragLeave={() => setDragOver(false)}
369
- onDrop={(e) => {
370
- e.preventDefault();
371
- setDragOver(false);
372
- const id = e.dataTransfer.getData('text/task-id');
373
- if (id) onMove(id, column.id);
374
- }}
375
- >
376
- <div className="kanban-col-header">
377
- <CardTitle>
378
- <span className={cn('status-dot', `status-${column.kind}`)} />
379
- {column.label}
380
- </CardTitle>
381
- <span className="kanban-col-count tabular-nums">{tasks.length}</span>
382
- </div>
383
- <div className="kanban-col-body">
384
- {tasks.length === 0 ? (
385
- <div className="kanban-empty">No tasks</div>
386
- ) : (
387
- tasks.map((t) => (
388
- <TaskCard
389
- key={t.id}
390
- task={t}
391
- onMove={(dir) => {
392
- const idx = COLUMNS.findIndex((c) => c.id === t.status);
393
- const next = idx + dir;
394
- if (next >= 0 && next < COLUMNS.length) onMove(t.id, COLUMNS[next].id);
395
- }}
396
- onEdit={() => onEdit(t)}
397
- onDelete={() => onDelete(t.id)}
398
- onRetry={() => onRetry(t.id)}
399
- onSubmitToOdin={() => onSubmitToOdin(t.id)}
400
- tick={tick}
401
- />
402
- ))
403
- )}
404
- </div>
405
- </div>
406
- );
407
- }
408
-
409
- // ─── Card ──────────────────────────────────────────────────────────────────
427
+ };
410
428
 
411
- function TaskCard({
429
+ function TaskRow({
412
430
  task,
413
- onMove,
431
+ column,
432
+ onTaskClick,
414
433
  onEdit,
415
434
  onDelete,
416
435
  onRetry,
417
436
  onSubmitToOdin,
418
437
  tick,
419
- }: {
420
- task: Task;
421
- onMove: (dir: -1 | 1) => void;
422
- onEdit: () => void;
423
- onDelete: () => void;
424
- onRetry: () => void;
425
- onSubmitToOdin: () => void;
426
- tick: number;
427
- }) {
438
+ }: RowProps) {
428
439
  const assignedAgent = task.workedBy || task.assignee || null;
440
+ const priority: PriorityKind = (PRIORITIES as readonly string[]).includes(task.priority as string)
441
+ ? (task.priority as PriorityKind)
442
+ : 'normal';
429
443
  return (
430
444
  <div
431
- className={cn('task-card', `priority-${task.priority}`)}
445
+ className={cx('tasks-wave3__row', PRIORITY_ROW_CLASS[priority])}
432
446
  data-task-id={task.id}
433
- draggable
434
- onDragStart={(e) => {
435
- e.dataTransfer.setData('text/task-id', task.id);
436
- e.dataTransfer.effectAllowed = 'move';
437
- }}
438
447
  data-tick={tick}
448
+ role="button"
449
+ tabIndex={0}
450
+ onClick={() => onTaskClick(task)}
451
+ onKeyDown={(e) => {
452
+ if (e.key === 'Enter' || e.key === ' ') {
453
+ e.preventDefault();
454
+ onTaskClick(task);
455
+ }
456
+ }}
439
457
  >
440
- <div className="task-card-head">
441
- <span
442
- className="priority-dot"
443
- style={{ background: priorityColors[task.priority] || 'var(--info)' }}
444
- />
445
- <div className="task-card-title">{task.title}</div>
458
+ <div className="tasks-wave3__row-title-row">
459
+ <div className="tasks-wave3__row-title" title={task.title}>
460
+ {task.title}
461
+ </div>
462
+ <Badge variant={STATUS_BADGE[column.kind]} size="sm" aria-label={`Status ${column.label}`}>
463
+ {column.label}
464
+ </Badge>
446
465
  </div>
447
- {task.description && (
448
- <div className="task-card-desc">{task.description.slice(0, 160)}</div>
449
- )}
450
- <div className="task-card-badges">
466
+
467
+ <Inline gap={1} align="center" className="tasks-wave3__row-badges">
468
+ <Badge variant={PRIORITY_BADGE[priority]} size="sm">
469
+ {priority}
470
+ </Badge>
451
471
  {assignedAgent && (
452
- <span className="task-card-badge" title={`Auto-assigned to @${assignedAgent}`}>
453
- <Bot size={10} /> @{assignedAgent}
454
- </span>
472
+ <Badge variant="neutral" size="sm">
473
+ <Bot size={10} aria-hidden /> @{assignedAgent}
474
+ </Badge>
455
475
  )}
456
- {task.timeSpent ? (
457
- <span className="task-card-badge">
458
- <Clock size={10} /> {Math.round((task.timeSpent || 0) / 60)}m
459
- </span>
460
- ) : null}
461
- {task.tags && task.tags.length > 0 && task.tags.slice(0, 3).map((tag) => (
462
- <Tag key={tag}>{tag}</Tag>
476
+ {(task.tags || []).slice(0, 2).map((tag) => (
477
+ <Badge key={tag} variant="neutral" size="sm">
478
+ #{tag}
479
+ </Badge>
463
480
  ))}
464
- {/* v6.0.0 — Cline agent team indicator. */}
465
- {(task.tags || []).some((t) => t.startsWith('team:')) && (
466
- <span className="task-card-badge team" title="Cline agent team">
467
- <Sparkles size={10} /> team
468
- </span>
469
- )}
470
- </div>
471
- <div className="task-card-footer">
472
- <span className="task-card-time tabular-nums muted">
473
- {formatRelative(task.createdAt)}
474
- </span>
475
- <div className="task-card-actions" onClick={(e) => e.stopPropagation()}>
476
- <button
477
- type="button"
478
- className="icon-btn"
479
- aria-label="Move left"
480
- title="Move left"
481
- onClick={() => onMove(-1)}
482
- >
483
- <ChevronLeft size={14} />
484
- </button>
485
- <button
486
- type="button"
487
- className="icon-btn"
488
- aria-label="Edit"
481
+ </Inline>
482
+
483
+ <div className="tasks-wave3__row-footer">
484
+ <span className="tasks-wave3__row-time">{formatRelative(task.createdAt)}</span>
485
+ <Inline
486
+ gap={1}
487
+ align="center"
488
+ className="tasks-wave3__row-actions"
489
+ onClick={(e) => e.stopPropagation()}
490
+ >
491
+ <IconButton
492
+ variant="ghost"
493
+ size="sm"
494
+ icon={<Edit2 size={12} aria-hidden />}
495
+ aria-label="Edit task"
489
496
  title="Edit"
490
497
  onClick={onEdit}
491
- >
492
- <Edit2 size={14} />
493
- </button>
494
- <button
495
- type="button"
496
- className="icon-btn"
498
+ />
499
+ <IconButton
500
+ variant="ghost"
501
+ size="sm"
502
+ icon={<Send size={12} aria-hidden />}
497
503
  aria-label="Submit to Odin"
498
504
  title="Re-delegate to Odin"
499
505
  onClick={onSubmitToOdin}
500
- >
501
- <Send size={14} />
502
- </button>
503
- <button
504
- type="button"
505
- className="icon-btn"
506
- aria-label="Retry"
506
+ />
507
+ <IconButton
508
+ variant="ghost"
509
+ size="sm"
510
+ icon={<RotateCw size={12} aria-hidden />}
511
+ aria-label="Retry task"
507
512
  title="Retry"
508
513
  onClick={onRetry}
509
- >
510
- <RotateCw size={14} />
511
- </button>
512
- <button
513
- type="button"
514
- className="icon-btn icon-btn-danger"
515
- aria-label="Delete"
514
+ />
515
+ <IconButton
516
+ variant="danger"
517
+ size="sm"
518
+ icon={<Trash2 size={12} aria-hidden />}
519
+ aria-label="Delete task"
516
520
  title="Delete"
517
521
  onClick={onDelete}
518
- >
519
- <Trash2 size={14} />
520
- </button>
521
- <button
522
- type="button"
523
- className="icon-btn"
524
- aria-label="Move right"
525
- title="Move right"
526
- onClick={() => onMove(1)}
527
- >
528
- <ChevronRight size={14} />
529
- </button>
530
- </div>
522
+ />
523
+ </Inline>
531
524
  </div>
532
525
  </div>
533
526
  );
@@ -560,7 +553,7 @@ function openCreateTaskModal(
560
553
  titleEl?.focus();
561
554
  return;
562
555
  }
563
- const priority = (priorityEl?.value || 'normal') as Priority;
556
+ const priority = (priorityEl?.value || 'normal') as PriorityKind;
564
557
  const tags = (tagsEl?.value || '')
565
558
  .split(',')
566
559
  .map((s) => s.trim())
@@ -575,6 +568,11 @@ function openCreateTaskModal(
575
568
  // No assignee / agent — Odin decides.
576
569
  });
577
570
  modal.close();
571
+ // Synthetic mousedown is intentional: it signals any open outside-
572
+ // click listeners (e.g. WorkspaceSelector, Notifications popover)
573
+ // that another surface has just stolen focus, so they collapse
574
+ // before the toast overlays the layout. The event carries no
575
+ // coordinates by design — those listeners only check the dispatch.
578
576
  if (typeof window !== 'undefined') {
579
577
  window.dispatchEvent(new MouseEvent('mousedown'));
580
578
  }
@@ -694,7 +692,7 @@ function openEditTaskModal(
694
692
  titleEl?.focus();
695
693
  return;
696
694
  }
697
- const priority = (priorityEl?.value || 'normal') as Priority;
695
+ const priority = (priorityEl?.value || 'normal') as PriorityKind;
698
696
  const tags = (tagsEl?.value || '')
699
697
  .split(',')
700
698
  .map((s) => s.trim())
@@ -708,6 +706,11 @@ function openEditTaskModal(
708
706
  tags,
709
707
  });
710
708
  modal.close();
709
+ // Synthetic mousedown is intentional: it signals any open outside-
710
+ // click listeners (e.g. WorkspaceSelector, Notifications popover)
711
+ // that another surface has just stolen focus, so they collapse
712
+ // before the toast overlays the layout. The event carries no
713
+ // coordinates by design — those listeners only check the dispatch.
711
714
  if (typeof window !== 'undefined') {
712
715
  window.dispatchEvent(new MouseEvent('mousedown'));
713
716
  }
@@ -788,4 +791,5 @@ function openEditTaskModal(
788
791
  ),
789
792
  });
790
793
  }
794
+
791
795
  export const Tasks = React.memo(TasksInner);