@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
@@ -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,43 @@ 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' },
75
99
  ];
76
100
 
77
- const PRIORITIES = ['low', 'normal', 'high'] as const;
78
- type Priority = (typeof PRIORITIES)[number];
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 })),
106
+ ];
107
+
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
+ function TasksInner({
123
+ snapshot,
124
+ refreshSnapshot,
125
+ }: Props) {
81
126
  const toast = useToast();
82
127
  const modal = useModal();
83
128
  const [tasks, setTasks] = useState<Task[]>(snapshot.tasks || []);
@@ -219,72 +264,63 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
219
264
  }
220
265
  };
221
266
 
267
+ const handleTaskClick = (task: Task) => {
268
+ openEditTaskModal(modal, toast, task, setTasks, reload, refreshSnapshot);
269
+ };
270
+
222
271
  return (
223
- <div className="view view-tasks">
272
+ <div className="view view-tasks tasks-wave3">
224
273
  {/* Hidden live region for screen-reader announcements of status changes. */}
225
274
  <div className="sr-only" role="status" aria-live="polite">
226
275
  {statusAnnouncement}
227
276
  </div>
228
- <header className="view-header">
229
- <div className="view-header-text">
230
- <h2 className="view-title">
231
- <CheckSquare size={18} /> Tasks ({sorted.length})
277
+ <header className="tasks-wave3__header">
278
+ <div>
279
+ <h2 className="tasks-wave3__title-row">
280
+ <CheckSquare size={18} aria-hidden /> Tasks ({sorted.length})
232
281
  </h2>
233
- <p className="view-subtitle">
282
+ <p className="tasks-wave3__subtitle">
234
283
  Add a task — title and description is all you need. Odin picks the agent and priority.
235
284
  </p>
236
285
  </div>
237
286
  </header>
238
287
 
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>
288
+ {/* Toolbar: search, priority filter, refresh, backlog toggle, new task */}
289
+ <div className="tasks-wave3__toolbar" role="toolbar" aria-label="Task filters">
290
+ <div className="tasks-wave3__toolbar-group">
291
+ <SearchInput
292
+ inputSize="sm"
293
+ value={filter}
294
+ onChange={((v: unknown) => setFilter(String(v))) as unknown as React.ComponentProps<typeof SearchInput>['onChange']}
295
+ placeholder="Search…"
296
+ aria-label="Search tasks"
297
+ />
263
298
  </div>
264
299
 
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"
300
+ <div className="tasks-wave3__toolbar-group">
301
+ <span className="tasks-wave3__toolbar-label" id="tasks-priority-filter-label">
302
+ Priority
303
+ </span>
304
+ <Select
305
+ inputSize="sm"
306
+ aria-labelledby="tasks-priority-filter-label"
307
+ options={PRIORITY_OPTIONS}
272
308
  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>
309
+ onChange={(e) => setPriorityFilter((e.target as HTMLSelectElement).value)}
310
+ />
281
311
  </div>
282
312
 
283
- <div className="tasks-toolbar-spacer" />
313
+ <div className="tasks-wave3__toolbar-spacer" />
284
314
 
285
- <div className="tasks-toolbar-group">
286
- <Button variant="ghost" size="sm" onClick={reload} title="Refresh" aria-label="Refresh tasks">
287
- <RefreshCw size={14} />
315
+ <div className="tasks-wave3__toolbar-group">
316
+ <Button
317
+ variant="ghost"
318
+ size="sm"
319
+ onClick={reload}
320
+ title="Refresh"
321
+ aria-label="Refresh tasks"
322
+ >
323
+ <RefreshCw size={14} aria-hidden />
288
324
  </Button>
289
325
  {backlogCount > 0 && (
290
326
  <Button
@@ -293,9 +329,11 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
293
329
  onClick={() => setShowBacklog((v) => !v)}
294
330
  title={showBacklog ? 'Hide backlog' : 'Show backlog'}
295
331
  >
296
- <Inbox size={14} />
332
+ <Inbox size={14} aria-hidden />
297
333
  Backlog
298
- <span className="badge">{backlogCount}</span>
334
+ <span className="tasks-wave3__column-count" aria-label={`${backlogCount} items`}>
335
+ {backlogCount}
336
+ </span>
299
337
  </Button>
300
338
  )}
301
339
  <Button
@@ -303,7 +341,7 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
303
341
  size="sm"
304
342
  onClick={() => openCreateTaskModal(modal, toast, setTasks, reload, refreshSnapshot)}
305
343
  >
306
- <Plus size={14} /> New task
344
+ <Plus size={14} aria-hidden /> New task
307
345
  </Button>
308
346
  </div>
309
347
  </div>
@@ -316,218 +354,167 @@ function TasksInner({ snapshot, refreshSnapshot, setActiveTab }: Props) {
316
354
  )}
317
355
 
318
356
  {loading ? (
319
- <div className="view-loading"><Spinner size="lg" /></div>
357
+ <LoadingState label="Loading tasks…" />
320
358
  ) : (
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>
359
+ <Grid cols={5} gap={3} className="tasks-wave3__kanban">
360
+ {COLUMNS.map((col) => {
361
+ const columnTasks = sorted.filter((t) => t.status === col.id);
362
+ return (
363
+ <Panel
364
+ key={col.id}
365
+ padding={3}
366
+ className="tasks-wave3__column"
367
+ title={
368
+ <span className="tasks-wave3__column-title">
369
+ <StatusDot variant={col.dotVariant} size="sm" label={col.label} />
370
+ {col.label}
371
+ </span>
372
+ }
373
+ actions={
374
+ <span
375
+ className="tasks-wave3__column-count"
376
+ aria-label={`${columnTasks.length} tasks`}
377
+ >
378
+ {columnTasks.length}
379
+ </span>
380
+ }
381
+ >
382
+ <Stack gap={2} className="tasks-wave3__column-body">
383
+ {columnTasks.length === 0 ? (
384
+ <div className="tasks-wave3__column-empty">No tasks</div>
385
+ ) : (
386
+ columnTasks.map((t) => (
387
+ <TaskRow
388
+ key={t.id}
389
+ task={t}
390
+ column={col}
391
+ onTaskClick={handleTaskClick}
392
+ onEdit={() => openEditTaskModal(modal, toast, t, setTasks, reload, refreshSnapshot)}
393
+ onDelete={() => deleteTask(t.id)}
394
+ onRetry={() => retryTask(t.id)}
395
+ onSubmitToOdin={() => submitToOdin(t.id)}
396
+ tick={tick}
397
+ />
398
+ ))
399
+ )}
400
+ </Stack>
401
+ </Panel>
402
+ );
403
+ })}
404
+ </Grid>
336
405
  )}
337
406
  </div>
338
407
  );
339
408
  }
340
409
 
341
- // ─── Column ────────────────────────────────────────────────────────────────
410
+ // ─── Row ─────────────────────────────────────────────────────────────────
342
411
 
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;
412
+ type RowProps = {
413
+ task: Task;
414
+ column: (typeof COLUMNS)[number];
415
+ onTaskClick: (task: Task) => void;
416
+ onEdit: () => void;
417
+ onDelete: () => void;
418
+ onRetry: () => void;
419
+ onSubmitToOdin: () => void;
360
420
  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 ──────────────────────────────────────────────────────────────────
421
+ };
410
422
 
411
- function TaskCard({
423
+ function TaskRow({
412
424
  task,
413
- onMove,
425
+ column,
426
+ onTaskClick,
414
427
  onEdit,
415
428
  onDelete,
416
429
  onRetry,
417
430
  onSubmitToOdin,
418
431
  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
- }) {
432
+ }: RowProps) {
428
433
  const assignedAgent = task.workedBy || task.assignee || null;
434
+ const priority: PriorityKind = (PRIORITIES as readonly string[]).includes(task.priority as string)
435
+ ? (task.priority as PriorityKind)
436
+ : 'normal';
429
437
  return (
430
438
  <div
431
- className={cn('task-card', `priority-${task.priority}`)}
439
+ className={cx('tasks-wave3__row')}
432
440
  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
441
  data-tick={tick}
442
+ role="button"
443
+ tabIndex={0}
444
+ onClick={() => onTaskClick(task)}
445
+ onKeyDown={(e) => {
446
+ if (e.key === 'Enter' || e.key === ' ') {
447
+ e.preventDefault();
448
+ onTaskClick(task);
449
+ }
450
+ }}
439
451
  >
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>
452
+ <div className="tasks-wave3__row-title-row">
453
+ <div className="tasks-wave3__row-title" title={task.title}>
454
+ {task.title}
455
+ </div>
456
+ <Badge variant={STATUS_BADGE[column.kind]} size="sm" aria-label={`Status ${column.label}`}>
457
+ {column.label}
458
+ </Badge>
446
459
  </div>
447
- {task.description && (
448
- <div className="task-card-desc">{task.description.slice(0, 160)}</div>
449
- )}
450
- <div className="task-card-badges">
460
+
461
+ <Inline gap={1} align="center" className="tasks-wave3__row-badges">
462
+ <Badge variant={PRIORITY_BADGE[priority]} size="sm">
463
+ {priority}
464
+ </Badge>
451
465
  {assignedAgent && (
452
- <span className="task-card-badge" title={`Auto-assigned to @${assignedAgent}`}>
453
- <Bot size={10} /> @{assignedAgent}
454
- </span>
466
+ <Badge variant="neutral" size="sm">
467
+ <Bot size={10} aria-hidden /> @{assignedAgent}
468
+ </Badge>
455
469
  )}
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>
470
+ {(task.tags || []).slice(0, 2).map((tag) => (
471
+ <Badge key={tag} variant="neutral" size="sm">
472
+ #{tag}
473
+ </Badge>
463
474
  ))}
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"
475
+ </Inline>
476
+
477
+ <div className="tasks-wave3__row-footer">
478
+ <span className="tasks-wave3__row-time">{formatRelative(task.createdAt)}</span>
479
+ <Inline
480
+ gap={1}
481
+ align="center"
482
+ className="tasks-wave3__row-actions"
483
+ onClick={(e) => e.stopPropagation()}
484
+ >
485
+ <IconButton
486
+ variant="ghost"
487
+ size="sm"
488
+ icon={<Edit2 size={12} aria-hidden />}
489
+ aria-label="Edit task"
489
490
  title="Edit"
490
491
  onClick={onEdit}
491
- >
492
- <Edit2 size={14} />
493
- </button>
494
- <button
495
- type="button"
496
- className="icon-btn"
492
+ />
493
+ <IconButton
494
+ variant="ghost"
495
+ size="sm"
496
+ icon={<Send size={12} aria-hidden />}
497
497
  aria-label="Submit to Odin"
498
498
  title="Re-delegate to Odin"
499
499
  onClick={onSubmitToOdin}
500
- >
501
- <Send size={14} />
502
- </button>
503
- <button
504
- type="button"
505
- className="icon-btn"
506
- aria-label="Retry"
500
+ />
501
+ <IconButton
502
+ variant="ghost"
503
+ size="sm"
504
+ icon={<RotateCw size={12} aria-hidden />}
505
+ aria-label="Retry task"
507
506
  title="Retry"
508
507
  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"
508
+ />
509
+ <IconButton
510
+ variant="danger"
511
+ size="sm"
512
+ icon={<Trash2 size={12} aria-hidden />}
513
+ aria-label="Delete task"
516
514
  title="Delete"
517
515
  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>
516
+ />
517
+ </Inline>
531
518
  </div>
532
519
  </div>
533
520
  );
@@ -560,7 +547,7 @@ function openCreateTaskModal(
560
547
  titleEl?.focus();
561
548
  return;
562
549
  }
563
- const priority = (priorityEl?.value || 'normal') as Priority;
550
+ const priority = (priorityEl?.value || 'normal') as PriorityKind;
564
551
  const tags = (tagsEl?.value || '')
565
552
  .split(',')
566
553
  .map((s) => s.trim())
@@ -575,6 +562,11 @@ function openCreateTaskModal(
575
562
  // No assignee / agent — Odin decides.
576
563
  });
577
564
  modal.close();
565
+ // Synthetic mousedown is intentional: it signals any open outside-
566
+ // click listeners (e.g. WorkspaceSelector, Notifications popover)
567
+ // that another surface has just stolen focus, so they collapse
568
+ // before the toast overlays the layout. The event carries no
569
+ // coordinates by design — those listeners only check the dispatch.
578
570
  if (typeof window !== 'undefined') {
579
571
  window.dispatchEvent(new MouseEvent('mousedown'));
580
572
  }
@@ -694,7 +686,7 @@ function openEditTaskModal(
694
686
  titleEl?.focus();
695
687
  return;
696
688
  }
697
- const priority = (priorityEl?.value || 'normal') as Priority;
689
+ const priority = (priorityEl?.value || 'normal') as PriorityKind;
698
690
  const tags = (tagsEl?.value || '')
699
691
  .split(',')
700
692
  .map((s) => s.trim())
@@ -708,6 +700,11 @@ function openEditTaskModal(
708
700
  tags,
709
701
  });
710
702
  modal.close();
703
+ // Synthetic mousedown is intentional: it signals any open outside-
704
+ // click listeners (e.g. WorkspaceSelector, Notifications popover)
705
+ // that another surface has just stolen focus, so they collapse
706
+ // before the toast overlays the layout. The event carries no
707
+ // coordinates by design — those listeners only check the dispatch.
711
708
  if (typeof window !== 'undefined') {
712
709
  window.dispatchEvent(new MouseEvent('mousedown'));
713
710
  }
@@ -788,4 +785,5 @@ function openEditTaskModal(
788
785
  ),
789
786
  });
790
787
  }
788
+
791
789
  export const Tasks = React.memo(TasksInner);