@polderlabs/bizar 7.0.4 → 8.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 (489) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/bizar-dash/DESIGN.md +729 -969
  3. package/bizar-dash/GLYPH.md +418 -0
  4. package/bizar-dash/PLAN.md +1135 -0
  5. package/bizar-dash/dist/assets/ActivityFeed-BRBtQOHB.js +6 -0
  6. package/bizar-dash/dist/assets/ActivityFeed-BRBtQOHB.js.map +1 -0
  7. package/bizar-dash/dist/assets/ActivityView-CwqhxBfh.js +1 -0
  8. package/bizar-dash/dist/assets/ActivityView-CwqhxBfh.js.map +1 -0
  9. package/bizar-dash/dist/assets/AgentActivity-6MxU-a9F.js +1 -0
  10. package/bizar-dash/dist/assets/AgentActivity-6MxU-a9F.js.map +1 -0
  11. package/bizar-dash/dist/assets/AgentsView-BYrOFdwl.js +8 -0
  12. package/bizar-dash/dist/assets/AgentsView-BYrOFdwl.js.map +1 -0
  13. package/bizar-dash/dist/assets/Badge-Cd61wsBa.js +1 -0
  14. package/bizar-dash/dist/assets/Badge-Cd61wsBa.js.map +1 -0
  15. package/bizar-dash/dist/assets/Card-C_bvqXdQ.js +1 -0
  16. package/bizar-dash/dist/assets/Card-C_bvqXdQ.js.map +1 -0
  17. package/bizar-dash/dist/assets/EmptyState-BWQuKnk9.js +1 -0
  18. package/bizar-dash/dist/assets/EmptyState-BWQuKnk9.js.map +1 -0
  19. package/bizar-dash/dist/assets/GoalsView-Dok0yt9S.js +11 -0
  20. package/bizar-dash/dist/assets/GoalsView-Dok0yt9S.js.map +1 -0
  21. package/bizar-dash/dist/assets/Grid-C62gCPqt.js +1 -0
  22. package/bizar-dash/dist/assets/Grid-C62gCPqt.js.map +1 -0
  23. package/bizar-dash/dist/assets/KanbanCard-CtTKP6ZX.js +21 -0
  24. package/bizar-dash/dist/assets/KanbanCard-CtTKP6ZX.js.map +1 -0
  25. package/bizar-dash/dist/assets/LibrariesView-BcyBQXiY.js +67 -0
  26. package/bizar-dash/dist/assets/LibrariesView-BcyBQXiY.js.map +1 -0
  27. package/bizar-dash/dist/assets/MemoryView-49wR4u-y.js +13 -0
  28. package/bizar-dash/dist/assets/MemoryView-49wR4u-y.js.map +1 -0
  29. package/bizar-dash/dist/assets/OverviewView-BtQ_UWYj.js +1 -0
  30. package/bizar-dash/dist/assets/OverviewView-BtQ_UWYj.js.map +1 -0
  31. package/bizar-dash/dist/assets/ProgressBar-CpvDeq9F.js +1 -0
  32. package/bizar-dash/dist/assets/ProgressBar-CpvDeq9F.js.map +1 -0
  33. package/bizar-dash/dist/assets/SettingsRow-DmIe5OUu.js +30 -0
  34. package/bizar-dash/dist/assets/SettingsRow-DmIe5OUu.js.map +1 -0
  35. package/bizar-dash/dist/assets/SettingsView-D1KmGWbi.js +48 -0
  36. package/bizar-dash/dist/assets/SettingsView-D1KmGWbi.js.map +1 -0
  37. package/bizar-dash/dist/assets/StatTile-BkIP32gG.js +11 -0
  38. package/bizar-dash/dist/assets/StatTile-BkIP32gG.js.map +1 -0
  39. package/bizar-dash/dist/assets/TasksView-Cba3GJ42.js +1 -0
  40. package/bizar-dash/dist/assets/TasksView-Cba3GJ42.js.map +1 -0
  41. package/bizar-dash/dist/assets/ViewHeader-D6c2cw1b.js +6 -0
  42. package/bizar-dash/dist/assets/ViewHeader-D6c2cw1b.js.map +1 -0
  43. package/bizar-dash/dist/assets/brain-DeoXNDE2.js +6 -0
  44. package/bizar-dash/dist/assets/brain-DeoXNDE2.js.map +1 -0
  45. package/bizar-dash/dist/assets/calendar-DRlQkGJc.js +6 -0
  46. package/bizar-dash/dist/assets/calendar-DRlQkGJc.js.map +1 -0
  47. package/bizar-dash/dist/assets/circle-check-DmYQ3vGp.js +6 -0
  48. package/bizar-dash/dist/assets/circle-check-DmYQ3vGp.js.map +1 -0
  49. package/bizar-dash/dist/assets/cmdk-BwN8hcKQ.js +80 -0
  50. package/bizar-dash/dist/assets/cmdk-BwN8hcKQ.js.map +1 -0
  51. package/bizar-dash/dist/assets/dnd-kit-DR2Qzzxk.js +5 -0
  52. package/bizar-dash/dist/assets/dnd-kit-DR2Qzzxk.js.map +1 -0
  53. package/bizar-dash/dist/assets/git-merge-OoqBKQer.js +6 -0
  54. package/bizar-dash/dist/assets/git-merge-OoqBKQer.js.map +1 -0
  55. package/bizar-dash/dist/assets/git-pull-request-CXBQTR2f.js +6 -0
  56. package/bizar-dash/dist/assets/git-pull-request-CXBQTR2f.js.map +1 -0
  57. package/bizar-dash/dist/assets/main-COdgjqbZ.css +1 -0
  58. package/bizar-dash/dist/assets/main-Cw6gybzS.js +109 -0
  59. package/bizar-dash/dist/assets/main-Cw6gybzS.js.map +1 -0
  60. package/bizar-dash/dist/assets/message-square-DUTeIQAR.js +6 -0
  61. package/bizar-dash/dist/assets/message-square-DUTeIQAR.js.map +1 -0
  62. package/bizar-dash/dist/assets/minus-CrStJTZ7.js +6 -0
  63. package/bizar-dash/dist/assets/minus-CrStJTZ7.js.map +1 -0
  64. package/bizar-dash/dist/assets/wrench-BJ1WIC4u.js +6 -0
  65. package/bizar-dash/dist/assets/wrench-BJ1WIC4u.js.map +1 -0
  66. package/bizar-dash/dist/{mobile.html → v8.html} +6 -9
  67. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  68. package/bizar-dash/src/server/server.mjs +1 -57
  69. package/bizar-dash/src/web/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  70. package/bizar-dash/src/web/v8/App.tsx +188 -0
  71. package/bizar-dash/src/web/v8/__tests__/App.test.tsx +102 -0
  72. package/bizar-dash/src/web/v8/__tests__/AppShell.test.tsx +79 -0
  73. package/bizar-dash/src/web/v8/__tests__/PageSkeleton.test.tsx +12 -0
  74. package/bizar-dash/src/web/v8/__tests__/SettingsView.test.tsx +84 -0
  75. package/bizar-dash/src/web/v8/__tests__/Sidebar.test.tsx +95 -0
  76. package/bizar-dash/src/web/v8/__tests__/activity.test.tsx +63 -0
  77. package/bizar-dash/src/web/v8/__tests__/agents.test.tsx +90 -0
  78. package/bizar-dash/src/web/v8/__tests__/controls.test.tsx +135 -0
  79. package/bizar-dash/src/web/v8/__tests__/cx.test.ts +25 -0
  80. package/bizar-dash/src/web/v8/__tests__/data.test.tsx +293 -0
  81. package/bizar-dash/src/web/v8/__tests__/feedback.test.tsx +213 -0
  82. package/bizar-dash/src/web/v8/__tests__/goals.test.tsx +105 -0
  83. package/bizar-dash/src/web/v8/__tests__/kanban.test.tsx +176 -0
  84. package/bizar-dash/src/web/v8/__tests__/libraries.test.tsx +70 -0
  85. package/bizar-dash/src/web/v8/__tests__/memory.test.tsx +60 -0
  86. package/bizar-dash/src/web/v8/__tests__/navigation.test.tsx +137 -0
  87. package/bizar-dash/src/web/v8/__tests__/settings.test.tsx +104 -0
  88. package/bizar-dash/src/web/v8/__tests__/theme.test.tsx +98 -0
  89. package/bizar-dash/src/web/v8/__tests__/views.test.tsx +112 -0
  90. package/bizar-dash/src/web/v8/main.tsx +28 -0
  91. package/bizar-dash/src/web/v8/shell/AppShell.tsx +90 -0
  92. package/bizar-dash/src/web/v8/shell/PageSkeleton.tsx +29 -0
  93. package/bizar-dash/src/web/v8/shell/Sidebar.tsx +361 -0
  94. package/bizar-dash/src/web/v8/shell/StatusBar.tsx +63 -0
  95. package/bizar-dash/src/web/v8/shell/Topbar.tsx +113 -0
  96. package/bizar-dash/src/web/v8/shell/index.ts +15 -0
  97. package/bizar-dash/src/web/v8/ui/activity/ActivityFeed.tsx +137 -0
  98. package/bizar-dash/src/web/v8/ui/agents/AgentActivity.tsx +101 -0
  99. package/bizar-dash/src/web/v8/ui/agents/AgentCard.tsx +158 -0
  100. package/bizar-dash/src/web/v8/ui/controls/Button.tsx +141 -0
  101. package/bizar-dash/src/web/v8/ui/controls/ButtonGroup.tsx +32 -0
  102. package/bizar-dash/src/web/v8/ui/controls/Checkbox.tsx +49 -0
  103. package/bizar-dash/src/web/v8/ui/controls/Field.tsx +78 -0
  104. package/bizar-dash/src/web/v8/ui/controls/Form.tsx +23 -0
  105. package/bizar-dash/src/web/v8/ui/controls/IconButton.tsx +112 -0
  106. package/bizar-dash/src/web/v8/ui/controls/Input.tsx +94 -0
  107. package/bizar-dash/src/web/v8/ui/controls/RadioGroup.tsx +74 -0
  108. package/bizar-dash/src/web/v8/ui/controls/Select.tsx +171 -0
  109. package/bizar-dash/src/web/v8/ui/controls/Slider.tsx +68 -0
  110. package/bizar-dash/src/web/v8/ui/controls/Switch.tsx +56 -0
  111. package/bizar-dash/src/web/v8/ui/controls/Textarea.tsx +64 -0
  112. package/bizar-dash/src/web/v8/ui/controls/Toggle.tsx +36 -0
  113. package/bizar-dash/src/web/v8/ui/controls/ToggleGroup.tsx +85 -0
  114. package/bizar-dash/src/web/v8/ui/data/Accordion.tsx +134 -0
  115. package/bizar-dash/src/web/v8/ui/data/Avatar.tsx +175 -0
  116. package/bizar-dash/src/web/v8/ui/data/Badge.tsx +93 -0
  117. package/bizar-dash/src/web/v8/ui/data/BarList.tsx +101 -0
  118. package/bizar-dash/src/web/v8/ui/data/Card.tsx +140 -0
  119. package/bizar-dash/src/web/v8/ui/data/Chip.tsx +96 -0
  120. package/bizar-dash/src/web/v8/ui/data/Kbd.tsx +42 -0
  121. package/bizar-dash/src/web/v8/ui/data/ProgressBar.tsx +93 -0
  122. package/bizar-dash/src/web/v8/ui/data/Sparkline.tsx +98 -0
  123. package/bizar-dash/src/web/v8/ui/data/StatTile.tsx +150 -0
  124. package/bizar-dash/src/web/v8/ui/data/Table.tsx +170 -0
  125. package/bizar-dash/src/web/v8/ui/data/Timeline.tsx +130 -0
  126. package/bizar-dash/src/web/v8/ui/data/ViewHeader.tsx +122 -0
  127. package/bizar-dash/src/web/v8/ui/feedback/Alert.tsx +95 -0
  128. package/bizar-dash/src/web/v8/ui/feedback/Banner.tsx +39 -0
  129. package/bizar-dash/src/web/v8/ui/feedback/ContextMenu.tsx +143 -0
  130. package/bizar-dash/src/web/v8/ui/feedback/Dialog.tsx +149 -0
  131. package/bizar-dash/src/web/v8/ui/feedback/Drawer.tsx +8 -0
  132. package/bizar-dash/src/web/v8/ui/feedback/DropdownMenu.tsx +231 -0
  133. package/bizar-dash/src/web/v8/ui/feedback/EmptyState.tsx +48 -0
  134. package/bizar-dash/src/web/v8/ui/feedback/Popover.tsx +56 -0
  135. package/bizar-dash/src/web/v8/ui/feedback/Sheet.tsx +103 -0
  136. package/bizar-dash/src/web/v8/ui/feedback/Skeleton.tsx +67 -0
  137. package/bizar-dash/src/web/v8/ui/feedback/Spinner.tsx +30 -0
  138. package/bizar-dash/src/web/v8/ui/feedback/Toast.tsx +66 -0
  139. package/bizar-dash/src/web/v8/ui/feedback/Tooltip.tsx +89 -0
  140. package/bizar-dash/src/web/v8/ui/goals/GoalCard.tsx +176 -0
  141. package/bizar-dash/src/web/v8/ui/goals/KeyResult.tsx +115 -0
  142. package/bizar-dash/src/web/v8/ui/index.ts +322 -0
  143. package/bizar-dash/src/web/v8/ui/kanban/KanbanBoard.tsx +80 -0
  144. package/bizar-dash/src/web/v8/ui/kanban/KanbanCard.tsx +247 -0
  145. package/bizar-dash/src/web/v8/ui/kanban/KanbanColumn.tsx +181 -0
  146. package/bizar-dash/src/web/v8/ui/kanban/KanbanContextMenu.tsx +122 -0
  147. package/bizar-dash/src/web/v8/ui/kanban/KanbanQuickAdd.tsx +141 -0
  148. package/bizar-dash/src/web/v8/ui/libraries/LibraryGrid.tsx +30 -0
  149. package/bizar-dash/src/web/v8/ui/libraries/LibraryItem.tsx +161 -0
  150. package/bizar-dash/src/web/v8/ui/memory/MemoryVault.tsx +144 -0
  151. package/bizar-dash/src/web/v8/ui/navigation/CommandPalette.tsx +224 -0
  152. package/bizar-dash/src/web/v8/ui/navigation/NavLink.tsx +81 -0
  153. package/bizar-dash/src/web/v8/ui/navigation/Pagination.tsx +164 -0
  154. package/bizar-dash/src/web/v8/ui/navigation/Tabs.tsx +128 -0
  155. package/bizar-dash/src/web/v8/ui/navigation/useCommandPaletteHotkey.test.ts +79 -0
  156. package/bizar-dash/src/web/v8/ui/primitives/Box.tsx +144 -0
  157. package/bizar-dash/src/web/v8/ui/primitives/Center.tsx +40 -0
  158. package/bizar-dash/src/web/v8/ui/primitives/Cluster.tsx +67 -0
  159. package/bizar-dash/src/web/v8/ui/primitives/Grid.tsx +104 -0
  160. package/bizar-dash/src/web/v8/ui/primitives/Inline.tsx +77 -0
  161. package/bizar-dash/src/web/v8/ui/primitives/Portal.tsx +33 -0
  162. package/bizar-dash/src/web/v8/ui/primitives/ScrollArea.tsx +62 -0
  163. package/bizar-dash/src/web/v8/ui/primitives/Separator.tsx +42 -0
  164. package/bizar-dash/src/web/v8/ui/primitives/Stack.tsx +73 -0
  165. package/bizar-dash/src/web/v8/ui/primitives/VisuallyHidden.tsx +49 -0
  166. package/bizar-dash/src/web/v8/ui/settings/SettingsNav.tsx +91 -0
  167. package/bizar-dash/src/web/v8/ui/settings/SettingsRow.tsx +88 -0
  168. package/bizar-dash/src/web/v8/ui/settings/SettingsSection.tsx +109 -0
  169. package/bizar-dash/src/web/v8/ui/styles/globals.css +220 -0
  170. package/bizar-dash/src/web/v8/ui/styles/reset.css +57 -0
  171. package/bizar-dash/src/web/v8/ui/styles/tokens.css +217 -0
  172. package/bizar-dash/src/web/v8/ui/theme/DensityProvider.test.tsx +74 -0
  173. package/bizar-dash/src/web/v8/ui/theme/DensityProvider.tsx +66 -0
  174. package/bizar-dash/src/web/v8/ui/theme/ThemeProvider.test.tsx +86 -0
  175. package/bizar-dash/src/web/v8/ui/theme/ThemeProvider.tsx +94 -0
  176. package/bizar-dash/src/web/v8/ui/theme/ThemeToggle.tsx +88 -0
  177. package/bizar-dash/src/web/v8/ui/theme/useDensity.ts +15 -0
  178. package/bizar-dash/src/web/v8/ui/theme/useTheme.ts +16 -0
  179. package/bizar-dash/src/web/v8/ui/utils/cx.ts +19 -0
  180. package/bizar-dash/src/web/v8/views/Activity/ActivityView.tsx +33 -0
  181. package/bizar-dash/src/web/v8/views/Agents/AgentsView.tsx +108 -0
  182. package/bizar-dash/src/web/v8/views/CommandPalette/AppCommandPalette.test.tsx +142 -0
  183. package/bizar-dash/src/web/v8/views/CommandPalette/AppCommandPalette.tsx +144 -0
  184. package/bizar-dash/src/web/v8/views/Goals/GoalsView.tsx +84 -0
  185. package/bizar-dash/src/web/v8/views/Libraries/LibrariesView.tsx +41 -0
  186. package/bizar-dash/src/web/v8/views/Memory/MemoryView.tsx +61 -0
  187. package/bizar-dash/src/web/v8/views/Overview/OverviewView.tsx +92 -0
  188. package/bizar-dash/src/web/v8/views/Router.tsx +96 -0
  189. package/bizar-dash/src/web/v8/views/Settings/SettingsView.tsx +305 -0
  190. package/bizar-dash/src/web/v8/views/Tasks/CommandPalettePlaceholder.tsx +106 -0
  191. package/bizar-dash/src/web/v8/views/Tasks/TasksKanbanPlaceholder.tsx +62 -0
  192. package/bizar-dash/src/web/v8/views/Tasks/TasksView.tsx +74 -0
  193. package/bizar-dash/src/web/v8/vite-env.d.ts +15 -0
  194. package/bizar-dash/src/web/{mobile.html → v8.html} +4 -7
  195. package/bizar-dash/tests/setup.ts +26 -0
  196. package/bizar-dash/vite.config.ts +26 -2
  197. package/bizar-dash/vitest.config.ts +5 -3
  198. package/package.json +36 -3
  199. package/bizar-dash/dist/assets/EnvVarsSection-BfhAz3ax.js +0 -114
  200. package/bizar-dash/dist/assets/EnvVarsSection-BfhAz3ax.js.map +0 -1
  201. package/bizar-dash/dist/assets/MobileChat-DLWdv6tQ.js +0 -1
  202. package/bizar-dash/dist/assets/MobileChat-DLWdv6tQ.js.map +0 -1
  203. package/bizar-dash/dist/assets/MobileSettings-Bxv62uPY.js +0 -1
  204. package/bizar-dash/dist/assets/MobileSettings-Bxv62uPY.js.map +0 -1
  205. package/bizar-dash/dist/assets/Toast-ATSAmbdi.js +0 -26
  206. package/bizar-dash/dist/assets/Toast-ATSAmbdi.js.map +0 -1
  207. package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js +0 -1
  208. package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js.map +0 -1
  209. package/bizar-dash/dist/assets/main-CJAtrosO.css +0 -1
  210. package/bizar-dash/dist/assets/main-DKKj9dsq.js +0 -430
  211. package/bizar-dash/dist/assets/main-DKKj9dsq.js.map +0 -1
  212. package/bizar-dash/dist/assets/mobile-C_naUN91.js +0 -1
  213. package/bizar-dash/dist/assets/mobile-C_naUN91.js.map +0 -1
  214. package/bizar-dash/dist/assets/mobile-layout-BWLtwtfQ.css +0 -1
  215. package/bizar-dash/dist/assets/mobile-layout-GZ3bE8L6.js +0 -211
  216. package/bizar-dash/dist/assets/mobile-layout-GZ3bE8L6.js.map +0 -1
  217. package/bizar-dash/dist/assets/useSlashCommands-BOwD7lNb.js +0 -65
  218. package/bizar-dash/dist/assets/useSlashCommands-BOwD7lNb.js.map +0 -1
  219. package/bizar-dash/dist/index.html +0 -43
  220. package/bizar-dash/src/web/App.tsx +0 -915
  221. package/bizar-dash/src/web/MobileApp.tsx +0 -414
  222. package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +0 -95
  223. package/bizar-dash/src/web/components/ArtifactListDialog.tsx +0 -55
  224. package/bizar-dash/src/web/components/ArtifactViewer.tsx +0 -144
  225. package/bizar-dash/src/web/components/AuditDialog.tsx +0 -79
  226. package/bizar-dash/src/web/components/AutosaveField.tsx +0 -50
  227. package/bizar-dash/src/web/components/BackupRestore.tsx +0 -330
  228. package/bizar-dash/src/web/components/BgStatusBadge.tsx +0 -32
  229. package/bizar-dash/src/web/components/Button.tsx +0 -56
  230. package/bizar-dash/src/web/components/CanvasContextMenu.tsx +0 -68
  231. package/bizar-dash/src/web/components/Card.tsx +0 -40
  232. package/bizar-dash/src/web/components/CollapsibleSection.tsx +0 -72
  233. package/bizar-dash/src/web/components/CommandDialog.tsx +0 -66
  234. package/bizar-dash/src/web/components/DoctorPanel.tsx +0 -160
  235. package/bizar-dash/src/web/components/EmptyState.tsx +0 -30
  236. package/bizar-dash/src/web/components/EnvVarManager.tsx +0 -430
  237. package/bizar-dash/src/web/components/EvalDiff.tsx +0 -157
  238. package/bizar-dash/src/web/components/EvalExportButton.tsx +0 -27
  239. package/bizar-dash/src/web/components/EvalRunCard.tsx +0 -78
  240. package/bizar-dash/src/web/components/FileBrowser.tsx +0 -633
  241. package/bizar-dash/src/web/components/HeadroomSettings.tsx +0 -418
  242. package/bizar-dash/src/web/components/HeadroomStatus.tsx +0 -158
  243. package/bizar-dash/src/web/components/HelpDialog.tsx +0 -55
  244. package/bizar-dash/src/web/components/InviteDialog.tsx +0 -205
  245. package/bizar-dash/src/web/components/KillConfirmDialog.tsx +0 -88
  246. package/bizar-dash/src/web/components/MobileBottomNav.tsx +0 -38
  247. package/bizar-dash/src/web/components/MobileDrawer.tsx +0 -68
  248. package/bizar-dash/src/web/components/MobileHeader.tsx +0 -50
  249. package/bizar-dash/src/web/components/MobileLayout.tsx +0 -63
  250. package/bizar-dash/src/web/components/Modal.tsx +0 -215
  251. package/bizar-dash/src/web/components/Notifications.tsx +0 -242
  252. package/bizar-dash/src/web/components/ScheduleTemplateCard.tsx +0 -72
  253. package/bizar-dash/src/web/components/ScreenshotCapture.tsx +0 -138
  254. package/bizar-dash/src/web/components/ScreenshotOCR.tsx +0 -42
  255. package/bizar-dash/src/web/components/SearchModal.tsx +0 -227
  256. package/bizar-dash/src/web/components/SettingsNav.tsx +0 -117
  257. package/bizar-dash/src/web/components/SettingsSearch.tsx +0 -328
  258. package/bizar-dash/src/web/components/Sidebar.tsx +0 -89
  259. package/bizar-dash/src/web/components/Spinner.tsx +0 -19
  260. package/bizar-dash/src/web/components/StatusBadge.tsx +0 -56
  261. package/bizar-dash/src/web/components/Tag.tsx +0 -41
  262. package/bizar-dash/src/web/components/TailscaleSettings.tsx +0 -161
  263. package/bizar-dash/src/web/components/TaskCard.tsx +0 -69
  264. package/bizar-dash/src/web/components/TaskCreateSheet.tsx +0 -76
  265. package/bizar-dash/src/web/components/Toast.tsx +0 -150
  266. package/bizar-dash/src/web/components/Toggle.tsx +0 -31
  267. package/bizar-dash/src/web/components/Topbar.tsx +0 -455
  268. package/bizar-dash/src/web/components/UsageChart.tsx +0 -250
  269. package/bizar-dash/src/web/components/UsageTable.tsx +0 -90
  270. package/bizar-dash/src/web/components/VirtualList.tsx +0 -53
  271. package/bizar-dash/src/web/components/VisualPlanDialog.tsx +0 -58
  272. package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +0 -211
  273. package/bizar-dash/src/web/components/VoiceRecorder.tsx +0 -105
  274. package/bizar-dash/src/web/components/WorkspaceSelector.tsx +0 -158
  275. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +0 -126
  276. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +0 -163
  277. package/bizar-dash/src/web/components/agents/QualityGates.tsx +0 -123
  278. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +0 -127
  279. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +0 -148
  280. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +0 -160
  281. package/bizar-dash/src/web/components/background/AttachButton.tsx +0 -96
  282. package/bizar-dash/src/web/components/background/TmuxAttachCard.tsx +0 -122
  283. package/bizar-dash/src/web/components/chat/AgentChip.tsx +0 -68
  284. package/bizar-dash/src/web/components/chat/AgentNode.tsx +0 -127
  285. package/bizar-dash/src/web/components/chat/AgentTree.tsx +0 -80
  286. package/bizar-dash/src/web/components/chat/ChatBubble.tsx +0 -60
  287. package/bizar-dash/src/web/components/chat/ChatComposer.tsx +0 -72
  288. package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +0 -370
  289. package/bizar-dash/src/web/components/chat/ChatRail.tsx +0 -387
  290. package/bizar-dash/src/web/components/chat/ChatThread.tsx +0 -127
  291. package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +0 -50
  292. package/bizar-dash/src/web/components/chat/Composer.tsx +0 -166
  293. package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +0 -54
  294. package/bizar-dash/src/web/components/chat/EmptyState.tsx +0 -21
  295. package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +0 -18
  296. package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +0 -38
  297. package/bizar-dash/src/web/components/chat/InfoPanel.tsx +0 -184
  298. package/bizar-dash/src/web/components/chat/JumpToLatest.tsx +0 -58
  299. package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +0 -19
  300. package/bizar-dash/src/web/components/chat/MessageBlock.tsx +0 -260
  301. package/bizar-dash/src/web/components/chat/MessageBubble.tsx +0 -60
  302. package/bizar-dash/src/web/components/chat/SessionList.tsx +0 -163
  303. package/bizar-dash/src/web/components/chat/SessionRowMenu.tsx +0 -206
  304. package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +0 -37
  305. package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +0 -55
  306. package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +0 -36
  307. package/bizar-dash/src/web/components/chat/_legacy.ts +0 -30
  308. package/bizar-dash/src/web/components/chat/index.ts +0 -31
  309. package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +0 -18
  310. package/bizar-dash/src/web/components/chat/useChat.ts +0 -1190
  311. package/bizar-dash/src/web/components/chat/useSlashCommands.ts +0 -47
  312. package/bizar-dash/src/web/components/goals/GoalInput.tsx +0 -86
  313. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +0 -111
  314. package/bizar-dash/src/web/components/tasks/BacklogPanel.css +0 -109
  315. package/bizar-dash/src/web/components/tasks/BacklogPanel.tsx +0 -209
  316. package/bizar-dash/src/web/hooks/useAutosave.ts +0 -107
  317. package/bizar-dash/src/web/hooks/useI18n.ts +0 -13
  318. package/bizar-dash/src/web/index.html +0 -37
  319. package/bizar-dash/src/web/lib/api.ts +0 -237
  320. package/bizar-dash/src/web/lib/goapPlanner.ts +0 -614
  321. package/bizar-dash/src/web/lib/i18n.ts +0 -25
  322. package/bizar-dash/src/web/lib/logger.ts +0 -30
  323. package/bizar-dash/src/web/lib/markdown.tsx +0 -59
  324. package/bizar-dash/src/web/lib/search.ts +0 -115
  325. package/bizar-dash/src/web/lib/types.ts +0 -894
  326. package/bizar-dash/src/web/lib/utils.ts +0 -114
  327. package/bizar-dash/src/web/lib/ws.ts +0 -177
  328. package/bizar-dash/src/web/locales/en.json +0 -52
  329. package/bizar-dash/src/web/main.tsx +0 -43
  330. package/bizar-dash/src/web/mobile/MobileApp.tsx +0 -309
  331. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +0 -38
  332. package/bizar-dash/src/web/mobile/MobileChat.tsx +0 -138
  333. package/bizar-dash/src/web/mobile/MobileDoctor.tsx +0 -80
  334. package/bizar-dash/src/web/mobile/MobileEval.tsx +0 -167
  335. package/bizar-dash/src/web/mobile/MobileMemory.tsx +0 -108
  336. package/bizar-dash/src/web/mobile/MobileOverview.tsx +0 -213
  337. package/bizar-dash/src/web/mobile/MobileSettings.tsx +0 -251
  338. package/bizar-dash/src/web/mobile/MobileTasks.tsx +0 -94
  339. package/bizar-dash/src/web/mobile/MobileTopbar.tsx +0 -115
  340. package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +0 -182
  341. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +0 -64
  342. package/bizar-dash/src/web/mobile/components/MobileModal.tsx +0 -123
  343. package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +0 -273
  344. package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +0 -374
  345. package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +0 -439
  346. package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +0 -143
  347. package/bizar-dash/src/web/mobile/views/MobileChat.tsx +0 -257
  348. package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +0 -175
  349. package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +0 -191
  350. package/bizar-dash/src/web/mobile/views/MobileMods.tsx +0 -149
  351. package/bizar-dash/src/web/mobile/views/MobileMore.tsx +0 -121
  352. package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +0 -137
  353. package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +0 -26
  354. package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +0 -685
  355. package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +0 -172
  356. package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +0 -363
  357. package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +0 -153
  358. package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +0 -564
  359. package/bizar-dash/src/web/mobile/views/QrCodePanel.tsx +0 -69
  360. package/bizar-dash/src/web/mobile.tsx +0 -14
  361. package/bizar-dash/src/web/styles/agents-redesign.css +0 -311
  362. package/bizar-dash/src/web/styles/chat.css +0 -1740
  363. package/bizar-dash/src/web/styles/glyphs.css +0 -960
  364. package/bizar-dash/src/web/styles/main.css +0 -10389
  365. package/bizar-dash/src/web/styles/memory.css +0 -1177
  366. package/bizar-dash/src/web/styles/minimax-usage.css +0 -842
  367. package/bizar-dash/src/web/styles/mobile-chat.css +0 -5
  368. package/bizar-dash/src/web/styles/mobile-layout.css +0 -233
  369. package/bizar-dash/src/web/styles/mobile.css +0 -2181
  370. package/bizar-dash/src/web/styles/settings.css +0 -775
  371. package/bizar-dash/src/web/styles/skills.css +0 -302
  372. package/bizar-dash/src/web/styles/tasks-redesign.css +0 -253
  373. package/bizar-dash/src/web/styles/tasks.css +0 -605
  374. package/bizar-dash/src/web/ui/controls/Button.tsx +0 -77
  375. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +0 -95
  376. package/bizar-dash/src/web/ui/controls/IconButton.tsx +0 -66
  377. package/bizar-dash/src/web/ui/controls/Kbd.tsx +0 -24
  378. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +0 -77
  379. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +0 -109
  380. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +0 -91
  381. package/bizar-dash/src/web/ui/controls/Select.tsx +0 -92
  382. package/bizar-dash/src/web/ui/controls/Slider.tsx +0 -70
  383. package/bizar-dash/src/web/ui/controls/TextInput.tsx +0 -67
  384. package/bizar-dash/src/web/ui/controls/Textarea.tsx +0 -56
  385. package/bizar-dash/src/web/ui/controls/Toggle.tsx +0 -79
  386. package/bizar-dash/src/web/ui/controls/controls.css +0 -476
  387. package/bizar-dash/src/web/ui/controls/index.ts +0 -35
  388. package/bizar-dash/src/web/ui/data/BarChart.tsx +0 -60
  389. package/bizar-dash/src/web/ui/data/DataTable.tsx +0 -216
  390. package/bizar-dash/src/web/ui/data/EmptyState.tsx +0 -60
  391. package/bizar-dash/src/web/ui/data/ErrorState.tsx +0 -56
  392. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +0 -106
  393. package/bizar-dash/src/web/ui/data/LoadingState.tsx +0 -65
  394. package/bizar-dash/src/web/ui/data/Sparkline.tsx +0 -87
  395. package/bizar-dash/src/web/ui/data/StatTile.tsx +0 -87
  396. package/bizar-dash/src/web/ui/data/data.css +0 -540
  397. package/bizar-dash/src/web/ui/data/index.ts +0 -31
  398. package/bizar-dash/src/web/ui/feedback/Badge.tsx +0 -49
  399. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +0 -209
  400. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +0 -69
  401. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +0 -50
  402. package/bizar-dash/src/web/ui/feedback/Toast.tsx +0 -200
  403. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +0 -126
  404. package/bizar-dash/src/web/ui/feedback/feedback.css +0 -314
  405. package/bizar-dash/src/web/ui/feedback/index.ts +0 -43
  406. package/bizar-dash/src/web/ui/index.ts +0 -57
  407. package/bizar-dash/src/web/ui/layout/AppShell.tsx +0 -51
  408. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +0 -97
  409. package/bizar-dash/src/web/ui/layout/Panel.tsx +0 -73
  410. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +0 -39
  411. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +0 -131
  412. package/bizar-dash/src/web/ui/layout/Tabs.tsx +0 -133
  413. package/bizar-dash/src/web/ui/layout/Topbar.tsx +0 -42
  414. package/bizar-dash/src/web/ui/layout/ViewHeader.tsx +0 -48
  415. package/bizar-dash/src/web/ui/layout/index.ts +0 -34
  416. package/bizar-dash/src/web/ui/layout/layout.css +0 -464
  417. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +0 -199
  418. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +0 -95
  419. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +0 -93
  420. package/bizar-dash/src/web/ui/navigation/index.ts +0 -17
  421. package/bizar-dash/src/web/ui/navigation/navigation.css +0 -343
  422. package/bizar-dash/src/web/ui/primitives/Box.tsx +0 -91
  423. package/bizar-dash/src/web/ui/primitives/Grid.tsx +0 -49
  424. package/bizar-dash/src/web/ui/primitives/Inline.tsx +0 -55
  425. package/bizar-dash/src/web/ui/primitives/Separator.tsx +0 -46
  426. package/bizar-dash/src/web/ui/primitives/Stack.tsx +0 -54
  427. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +0 -26
  428. package/bizar-dash/src/web/ui/primitives/index.ts +0 -23
  429. package/bizar-dash/src/web/ui/primitives/primitives.css +0 -183
  430. package/bizar-dash/src/web/ui/styles/globals.css +0 -86
  431. package/bizar-dash/src/web/ui/styles/reset.css +0 -125
  432. package/bizar-dash/src/web/ui/styles/tokens.css +0 -208
  433. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +0 -142
  434. package/bizar-dash/src/web/ui/theme/tokens.ts +0 -131
  435. package/bizar-dash/src/web/ui/theme/useTheme.ts +0 -22
  436. package/bizar-dash/src/web/ui/utils/cx.ts +0 -42
  437. package/bizar-dash/src/web/views/Activity.tsx +0 -1221
  438. package/bizar-dash/src/web/views/Agents.tsx +0 -964
  439. package/bizar-dash/src/web/views/Artifacts.tsx +0 -1494
  440. package/bizar-dash/src/web/views/BackgroundAgents.tsx +0 -822
  441. package/bizar-dash/src/web/views/Chat.tsx +0 -532
  442. package/bizar-dash/src/web/views/Config.tsx +0 -3
  443. package/bizar-dash/src/web/views/Doctor.tsx +0 -317
  444. package/bizar-dash/src/web/views/Eval.tsx +0 -372
  445. package/bizar-dash/src/web/views/EvalReport.tsx +0 -353
  446. package/bizar-dash/src/web/views/GoalPlanner.tsx +0 -158
  447. package/bizar-dash/src/web/views/Harness.tsx +0 -306
  448. package/bizar-dash/src/web/views/History.tsx +0 -353
  449. package/bizar-dash/src/web/views/Memory.tsx +0 -193
  450. package/bizar-dash/src/web/views/MiniMaxUsage.tsx +0 -942
  451. package/bizar-dash/src/web/views/ModView.tsx +0 -207
  452. package/bizar-dash/src/web/views/Mods.tsx +0 -706
  453. package/bizar-dash/src/web/views/Overview.tsx +0 -1343
  454. package/bizar-dash/src/web/views/Providers.tsx +0 -302
  455. package/bizar-dash/src/web/views/Schedules.tsx +0 -902
  456. package/bizar-dash/src/web/views/Settings.tsx +0 -308
  457. package/bizar-dash/src/web/views/Skills.tsx +0 -383
  458. package/bizar-dash/src/web/views/SpawnAgentModal.tsx +0 -221
  459. package/bizar-dash/src/web/views/Tasks.tsx +0 -789
  460. package/bizar-dash/src/web/views/Workspace.tsx +0 -294
  461. package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +0 -844
  462. package/bizar-dash/src/web/views/glyphs/components.tsx +0 -1098
  463. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +0 -426
  464. package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +0 -23
  465. package/bizar-dash/src/web/views/memory/GitSyncPanel.tsx +0 -220
  466. package/bizar-dash/src/web/views/memory/LightragPanel.tsx +0 -310
  467. package/bizar-dash/src/web/views/memory/MemoryGraphLegend.tsx +0 -29
  468. package/bizar-dash/src/web/views/memory/MemoryGraphPanel.tsx +0 -192
  469. package/bizar-dash/src/web/views/memory/MemoryGraphView.tsx +0 -336
  470. package/bizar-dash/src/web/views/memory/MemoryOverview.tsx +0 -367
  471. package/bizar-dash/src/web/views/memory/MemoryStatusCard.tsx +0 -160
  472. package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +0 -650
  473. package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +0 -197
  474. package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +0 -101
  475. package/bizar-dash/src/web/views/settings/ActivitySection.tsx +0 -205
  476. package/bizar-dash/src/web/views/settings/AgentSection.tsx +0 -305
  477. package/bizar-dash/src/web/views/settings/AuthSection.tsx +0 -159
  478. package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +0 -15
  479. package/bizar-dash/src/web/views/settings/GeneralSection.tsx +0 -128
  480. package/bizar-dash/src/web/views/settings/HeadroomSection.tsx +0 -39
  481. package/bizar-dash/src/web/views/settings/MemorySection.tsx +0 -154
  482. package/bizar-dash/src/web/views/settings/NetworkSection.tsx +0 -87
  483. package/bizar-dash/src/web/views/settings/NotificationsSection.tsx +0 -34
  484. package/bizar-dash/src/web/views/settings/SystemLlmSection.tsx +0 -81
  485. package/bizar-dash/src/web/views/settings/ThemeSection.tsx +0 -168
  486. package/bizar-dash/src/web/views/settings/UpdatesSection.tsx +0 -256
  487. package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +0 -189
  488. package/packages/sdk/.harness/agents.json +0 -17
  489. package/packages/sdk/.harness/topology.json +0 -4
@@ -1,1190 +0,0 @@
1
- // src/components/chat/useChat.ts — shared chat state: messages, sessions,
2
- // send, polling, SSE, session-state tracking.
3
- //
4
- // v6.3.0 — Claude Code migration. Supports two parallel message streams:
5
- // - bizar (GET /api/chat)
6
- // - claude (GET /api/claude-sessions/:id/messages + SSE)
7
- //
8
- // Changes from v4.2.5:
9
- // * Endpoint prefix changed: `/api/claude-sessions/*` → `/api/claude-sessions/*`
10
- // * SSE event type names changed to match the Claude SDK envelope
11
- // (`message.part.updated` → `delta`/`text_delta`, etc.)
12
- // * Server no longer needs the `clineErrorInfo` 503 path; the
13
- // `claudeErrorInfo` field stays for parity but the dashboard
14
- // mostly sees 502s now (claude CLI not on PATH / spawn failed).
15
- // * Optimistic send uses a stable `msg_*` ID and is deduped when
16
- // claude echoes the message back via SDK events.
17
- // * `claudeMessages` keeps server-provided IDs distinct from
18
- // optimistic IDs so the merge is unambiguous (server echo
19
- // replaces optimistic by matching messageID; otherwise both stick
20
- // around briefly until the user/session state catches up).
21
- // * `onSend` routes to `/claude-sessions/:id/send` when a
22
- // claude session is active, `/chat` otherwise. Errors surface
23
- // via toast + we always remove the optimistic placeholder.
24
- // * Sessions are managed by source:
25
- // - `bizarSessions` → local .jsonl store, POST /chat/sessions
26
- // - `claudeSessions` → Claude Code, GET /claude-sessions
27
- // The rail shows both side-by-side with a source indicator and
28
- // uses separate rename/delete endpoints per source.
29
- // * All async actions surface a single `busy: { create, send, rename, delete }`
30
- // so the rail can disable buttons properly.
31
- // * No `any` leaks — all SSE event shapes are typed via
32
- // `ClineSseEnvelope`.
33
-
34
- import { useCallback, useEffect, useRef, useState } from 'react';
35
- import type {
36
- ChatMessage,
37
- ChatResponse,
38
- ChatSession,
39
- Settings,
40
- Snapshot,
41
- } from '../../lib/types';
42
- import { api, ApiError } from '../../lib/api';
43
- import type { SessionState } from './ChatRail';
44
- import type { AgentTreeNode } from './AgentNode';
45
-
46
- export interface SessionDisplayState {
47
- /** idle / streaming / awaiting — derived from the latest SSE event. */
48
- state: SessionState;
49
- /** Number of unread (i.e. arrived while the user wasn't viewing) messages. */
50
- unread: number;
51
- /** Pinned? (cosmetic for now; pinned state lives in the parent.) */
52
- pinned: boolean;
53
- /** Sub-agent tree, if any. */
54
- tree?: { root: AgentTreeNode };
55
- }
56
-
57
- /** Per-source action busy flags. Used by rail/info-panel to disable buttons. */
58
- export interface ChatBusyState {
59
- create: boolean;
60
- send: boolean;
61
- rename: boolean;
62
- delete: boolean;
63
- }
64
-
65
- /**
66
- * Structured error envelope surfaced by `loadClineSession` when
67
- * the upstream `/api/claude-sessions/:id/messages` call fails.
68
- *
69
- * - `code` mirrors the server's `error` field (`plugin_offline`,
70
- * `directory_unknown`, `cline_error`, `bad_request`).
71
- * - `cause` is the server's structured cause (`network`, `timeout`,
72
- * `unknown`, or a raw ECONNREFUSED-style string).
73
- * - `suggestion` is the operator-friendly hint from the server
74
- * (e.g. "Run `bizar doctor` for diagnostics").
75
- * - `canRetry` is `true` whenever the failure is transient (network
76
- * error, upstream 502) — i.e. the user can fix it by trying again
77
- * after starting the plugin / waiting for cline to come back.
78
- * False for hard failures (bad_request).
79
- */
80
- export interface ClineErrorInfo {
81
- type: 'session_load_failed';
82
- code: string;
83
- message: string;
84
- cause?: string;
85
- status?: number;
86
- suggestion?: string | null;
87
- canRetry: boolean;
88
- }
89
-
90
- /** Shape of one cline SSE envelope after unwrapping. */
91
- interface ClineSseEnvelope {
92
- type: string;
93
- sessionID?: string;
94
- messageID?: string;
95
- /** Raw upstream payload (kept for forwarders that want full fidelity). */
96
- data?: unknown;
97
- }
98
-
99
- /**
100
- * The chat hook returns a stable bag of state + actions used by
101
- * both Chat.tsx (desktop) and MobileChat.tsx (mobile).
102
- *
103
- * The hook owns the lifecycle of the cline SSE stream. Callers
104
- * should never instantiate their own EventSource for the same
105
- * session — they should always go through this hook.
106
- */
107
- export function useChat(snapshot: Snapshot, settings: Settings, initialTaskId?: string | null) {
108
- // ── Bizar stream ────────────────────────────────────────────────────────────
109
- const [bizarMessages, setBizarMessages] = useState<ChatMessage[]>([]);
110
- const [sessions, setSessions] = useState<ChatSession[]>([]);
111
- const [sessionId, setSessionId] = useState('');
112
- const [loading, setLoading] = useState(true);
113
- const [busy, setBusy] = useState<ChatBusyState>({
114
- create: false,
115
- send: false,
116
- rename: false,
117
- delete: false,
118
- });
119
-
120
- // ── Cline stream ─────────────────────────────────────────────────────────
121
- const [clineSessions, setClineSessions] = useState<ChatSession[]>([]);
122
- const [clineMessages, setClineMessages] = useState<ChatMessage[]>([]);
123
- const [activeSource, setActiveSource] = useState<'bizar' | 'cline' | null>(null);
124
- const [activeClineSessionId, setActiveClineSessionId] = useState<string | null>(null);
125
- const [clineError, setClineError] = useState<string | null>(null);
126
- const [clineSuggestion, setClineSuggestion] = useState<string | null>(null);
127
- /**
128
- * Structured error info for the cline session load path. New in
129
- * v5.0.0 — `ChatInfoPanel` consumes this to render its own error
130
- * section (in addition to the chat-thread-header error already
131
- * rendered by Chat.tsx). `null` when no error.
132
- */
133
- const [clineErrorInfo, setClineErrorInfo] = useState<ClineErrorInfo | null>(null);
134
-
135
- // ── v3.22 — session-state, unread, tree per session ────────────────────────
136
- const [sessionStates, setSessionStates] = useState<Record<string, SessionDisplayState>>({});
137
- const [stickToBottom, setStickToBottom] = useState(true);
138
- const [newMessageCount, setNewMessageCount] = useState(0);
139
-
140
- // Set of messageIDs the user has already accepted (or that arrived
141
- // via SSE) — used by the cline path to dedupe optimistic + echo.
142
- const [seenClineMessages, setSeenClineMessages] = useState<Set<string>>(new Set());
143
-
144
- // Backwards-compat alias (chat.sendSession delete etc. used `pinned` historically).
145
- const [pinned, setPinned] = useState<Set<number>>(new Set());
146
-
147
- const listRef = useRef<HTMLDivElement>(null);
148
- const toastRef = useRef<
149
- | {
150
- error: (msg: string) => void;
151
- success: (msg: string) => void;
152
- info: (msg: string) => void;
153
- warning: (msg: string) => void;
154
- }
155
- | null
156
- >(null);
157
-
158
- // ── SSE lifecycle refs ──────────────────────────────────────────────────────
159
- const clineEsRef = useRef<EventSource | null>(null);
160
- const sseReconnectAttemptRef = useRef(0);
161
- const sseReconnectTimerRef = useRef<number | null>(null);
162
- // The id of the session we're CURRENTLY streaming. When the user
163
- // switches sessions, this id changes and the SSE handler short-
164
- // circuits any in-flight appends.
165
- const sseCurrentSessionIdRef = useRef<string | null>(null);
166
- // Whether the SSE connection should be auto-reconnected on error.
167
- // Set to false on explicit disconnect (session switch / unmount).
168
- const sseAutoReconnectRef = useRef(false);
169
-
170
- /** Append `msg` to `clineMessages`, replacing any prior
171
- * optimistic entry whose content matches or whose ID matches. */
172
- const appendClineMessage = useCallback((msg: ChatMessage) => {
173
- if (!msg) return;
174
- setClineMessages((cur) => {
175
- // Existing exact-ID match: skip.
176
- if (msg.id && cur.some((m) => m.id === msg.id)) return cur;
177
- // Optimistic placeholder (starts with msg_) with the same role
178
- // and content: replace it. This is how cline's echo of our
179
- // own message gets deduplicated against the optimistic copy.
180
- const idx = cur.findIndex(
181
- (m) =>
182
- typeof m.id === 'string' &&
183
- m.id.startsWith('msg_') &&
184
- m.role === msg.role &&
185
- (m.content || '') === (msg.content || ''),
186
- );
187
- if (idx >= 0) {
188
- const next = cur.slice();
189
- next[idx] = { ...msg, id: msg.id ?? cur[idx].id };
190
- return next;
191
- }
192
- return [...cur, msg];
193
- });
194
- }, []);
195
-
196
- /** Mark a messageID as seen — used to dedupe echo rounds. */
197
- const markSeen = useCallback((id: string | undefined) => {
198
- if (!id) return;
199
- setSeenClineMessages((cur) => {
200
- if (cur.has(id)) return cur;
201
- const next = new Set(cur);
202
- next.add(id);
203
- return next;
204
- });
205
- }, []);
206
-
207
- // ── Helpers to mutate per-session state ────────────────────────────────────
208
- const setSessionDisplayState = useCallback(
209
- (id: string, patch: Partial<SessionDisplayState>) => {
210
- setSessionStates((cur) => {
211
- const prev: SessionDisplayState =
212
- cur[id] ?? { state: 'idle', unread: 0, pinned: false };
213
- return { ...cur, [id]: { ...prev, ...patch } };
214
- });
215
- },
216
- [],
217
- );
218
-
219
- const markSessionStreaming = useCallback(
220
- (id: string, streaming: boolean) => {
221
- setSessionDisplayState(id, { state: streaming ? 'streaming' : 'idle' });
222
- },
223
- [setSessionDisplayState],
224
- );
225
-
226
- /** ─── Toast binding (set by the consuming view via `setToast`) ──── */
227
- const setToast = useCallback(
228
- (toast: typeof toastRef.current) => {
229
- toastRef.current = toast;
230
- },
231
- [],
232
- );
233
-
234
- // ── Load bizar chat ─────────────────────────────────────────────────────────
235
- const loadChat = useCallback(
236
- async (sid?: string) => {
237
- try {
238
- const url = sid
239
- ? `/chat?session=${encodeURIComponent(sid)}`
240
- : '/chat?limit=200';
241
- const data = await api.get<ChatResponse>(url);
242
- setBizarMessages(data.messages || []);
243
- setSessions(data.sessions || []);
244
- } catch (err) {
245
- toastRef.current?.error(
246
- `Chat load failed: ${(err as Error).message}`,
247
- );
248
- } finally {
249
- setLoading(false);
250
- }
251
- },
252
- [],
253
- );
254
-
255
- const loadTaskChat = useCallback(async (taskId: string) => {
256
- setLoading(true);
257
- try {
258
- const data = await api.get<{
259
- sessionId?: string;
260
- messages: ChatMessage[];
261
- }>(`/tasks/${encodeURIComponent(taskId)}/chat`);
262
- setBizarMessages(data.messages || []);
263
- setSessionId(data.sessionId || taskId);
264
- } catch (err) {
265
- toastRef.current?.error(
266
- `Task chat load failed: ${(err as Error).message}`,
267
- );
268
- } finally {
269
- setLoading(false);
270
- }
271
- }, []);
272
-
273
- const refreshSessions = useCallback(async () => {
274
- try {
275
- const data = await api.get<{ sessions: ChatSession[] }>('/chat/sessions');
276
- setSessions(data.sessions || []);
277
- } catch {
278
- /* best-effort */
279
- }
280
- }, []);
281
-
282
- const refreshClineSessions = useCallback(async () => {
283
- try {
284
- const data = await api.get<{ sessions: ChatSession[] }>(
285
- '/claude-sessions',
286
- );
287
- // Enrich with source marker.
288
- const enriched: ChatSession[] = (data.sessions || []).map((s) => ({
289
- ...s,
290
- source: 'cline' as const,
291
- title: s.title || s.id,
292
- }));
293
- setClineSessions(enriched);
294
- } catch {
295
- /* best-effort */
296
- }
297
- }, []);
298
-
299
- // ── Cline SSE connection ─────────────────────────────────────────────────
300
- //
301
- // Single source of truth for the EventSource lifecycle. Callers:
302
- // - `loadClineSession(id)` — close any current, open a new one.
303
- // - `closeClineSession()` — close and stop auto-reconnect.
304
- // - unmount — close and stop.
305
- //
306
- // On error / network drop, the EventSource triggers `onerror`. We
307
- // close the connection and schedule a reconnect (with exponential
308
- // backoff: 1s, 2s, 4s, …, capped at 30s) UNLESS `sseAutoReconnectRef`
309
- // is false (explicit disconnect).
310
- //
311
- const openSseForSession = useCallback(
312
- (id: string) => {
313
- // Close any existing connection first.
314
- if (clineEsRef.current) {
315
- try {
316
- clineEsRef.current.close();
317
- } catch {
318
- /* noop */
319
- }
320
- clineEsRef.current = null;
321
- }
322
- if (sseReconnectTimerRef.current !== null) {
323
- clearTimeout(sseReconnectTimerRef.current);
324
- sseReconnectTimerRef.current = null;
325
- }
326
-
327
- // Build the SSE URL. EventSource can't set headers, so we
328
- // pass the auth token as ?token= when set (loopback makes this
329
- // optional, but harmless).
330
- const tok = api.getToken();
331
- const url = tok
332
- ? `/api/claude-sessions/${encodeURIComponent(id)}/stream?token=${encodeURIComponent(tok)}`
333
- : `/api/claude-sessions/${encodeURIComponent(id)}/stream`;
334
-
335
- sseCurrentSessionIdRef.current = id;
336
- sseAutoReconnectRef.current = true;
337
- sseReconnectAttemptRef.current = 0;
338
- setClineError(null);
339
- setClineSuggestion(null);
340
- setClineErrorInfo(null);
341
-
342
- const connect = () => {
343
- if (!sseAutoReconnectRef.current) return;
344
- if (sseCurrentSessionIdRef.current !== id) return;
345
- const es = new EventSource(url);
346
- clineEsRef.current = es;
347
-
348
- // Helper: dispatch one parsed envelope to local state.
349
- const onEnvelope = (evt: ClineSseEnvelope | null) => {
350
- if (!evt || !evt.type) return;
351
- // Drop events for OTHER sessions even though the proxy
352
- // already filters — defense in depth.
353
- if (evt.sessionID && evt.sessionID !== id) return;
354
-
355
- const ts = new Date().toISOString();
356
- const sidForState = id;
357
-
358
- if (
359
- evt.type === 'message.user.created' ||
360
- evt.type === 'message.user.updated'
361
- ) {
362
- const payload = evt.data as
363
- | { messageID?: string; data?: { content?: string } }
364
- | undefined;
365
- appendClineMessage({
366
- id: payload?.messageID ?? evt.messageID,
367
- role: 'user',
368
- content: payload?.data?.content ?? '',
369
- ts,
370
- });
371
- markSeen(payload?.messageID ?? evt.messageID);
372
- return;
373
- }
374
- if (
375
- evt.type === 'message.assistant.created' ||
376
- evt.type === 'message.assistant.updated'
377
- ) {
378
- const payload = evt.data as
379
- | { messageID?: string; data?: { content?: string } }
380
- | undefined;
381
- appendClineMessage({
382
- id: payload?.messageID ?? evt.messageID,
383
- role: 'assistant',
384
- content: payload?.data?.content ?? '',
385
- ts,
386
- });
387
- markSeen(payload?.messageID ?? evt.messageID);
388
- markSessionStreaming(sidForState, true);
389
- return;
390
- }
391
- if (evt.type === 'message.part.updated') {
392
- const payload = evt.data as
393
- | {
394
- part?: { type?: string; text?: string };
395
- messageID?: string;
396
- id?: string;
397
- }
398
- | undefined;
399
- const text = payload?.part?.text ?? '';
400
- if (!text) return;
401
- appendClineMessage({
402
- id: payload?.id ?? payload?.messageID ?? evt.messageID,
403
- role: 'assistant',
404
- content: text,
405
- ts,
406
- });
407
- markSeen(payload?.id ?? payload?.messageID ?? evt.messageID);
408
- markSessionStreaming(sidForState, true);
409
- return;
410
- }
411
- if (
412
- evt.type === 'session.idle' ||
413
- evt.type === 'message.assistant.completed' ||
414
- evt.type === 'session.completed'
415
- ) {
416
- markSessionStreaming(sidForState, false);
417
- return;
418
- }
419
- if (
420
- evt.type === 'session.awaiting' ||
421
- evt.type === 'session.awaiting_input' ||
422
- evt.type === 'message.assistant.awaiting'
423
- ) {
424
- setSessionDisplayState(sidForState, { state: 'awaiting' });
425
- return;
426
- }
427
- if (evt.type === 'session.error') {
428
- setClineError(
429
- (evt.data as { message?: string } | undefined)?.message ||
430
- 'cline session error',
431
- );
432
- return;
433
- }
434
- // Other event types are intentionally ignored — the proxy
435
- // is canonical, we don't need to project every upstream
436
- // event into local state.
437
- };
438
-
439
- // Stable parser — the server may send both `{type: "x"}` and
440
- // `sync` envelopes with a `.<n>` version suffix. We accept both.
441
- const parseData = (raw: string | null | undefined):
442
- | ClineSseEnvelope
443
- | null => {
444
- if (!raw) return null;
445
- try {
446
- const parsed = JSON.parse(raw) as Record<string, unknown>;
447
- // Direct shape: {type, properties: {...}}
448
- if (typeof parsed.type === 'string') {
449
- const props = parsed.properties as
450
- | Record<string, unknown>
451
- | undefined;
452
- const data = (parsed.data as Record<string, unknown> | undefined) ?? props;
453
- const sessionID = (
454
- typeof parsed.sessionID === 'string'
455
- ? parsed.sessionID
456
- : typeof props?.sessionID === 'string'
457
- ? (props.sessionID as string)
458
- : typeof data?.sessionID === 'string'
459
- ? (data.sessionID as string)
460
- : undefined
461
- );
462
- const messageID = (
463
- typeof parsed.messageID === 'string'
464
- ? parsed.messageID
465
- : typeof props?.messageID === 'string'
466
- ? (props.messageID as string)
467
- : typeof data?.messageID === 'string'
468
- ? (data.messageID as string)
469
- : undefined
470
- );
471
- let type = parsed.type as string;
472
- const m = /\.\d+$/.exec(type);
473
- if (m) type = type.slice(0, m.index);
474
- return { type, sessionID, messageID, data: parsed };
475
- }
476
- // Sync envelope: {type: "sync", syncEvent: {...}}
477
- if (
478
- parsed.type === 'sync' &&
479
- parsed.syncEvent &&
480
- typeof parsed.syncEvent === 'object'
481
- ) {
482
- const inner = parsed.syncEvent as Record<string, unknown>;
483
- let type = (inner.type as string) || '';
484
- const m = /\.\d+$/.exec(type);
485
- if (m) type = type.slice(0, m.index);
486
- const innerData = (inner.data as Record<string, unknown>) || {};
487
- return {
488
- type,
489
- sessionID:
490
- typeof innerData.sessionID === 'string'
491
- ? innerData.sessionID
492
- : undefined,
493
- messageID:
494
- typeof innerData.messageID === 'string'
495
- ? innerData.messageID
496
- : undefined,
497
- data: inner,
498
- };
499
- }
500
- return null;
501
- } catch {
502
- return null;
503
- }
504
- };
505
-
506
- // register handlers per named event type
507
- const namedTypes = [
508
- 'message.user.created',
509
- 'message.user.updated',
510
- 'message.assistant.created',
511
- 'message.assistant.updated',
512
- 'message.part.updated',
513
- 'session.idle',
514
- 'message.assistant.completed',
515
- 'session.completed',
516
- 'session.awaiting',
517
- 'session.awaiting_input',
518
- 'message.assistant.awaiting',
519
- 'session.error',
520
- ];
521
- for (const name of namedTypes) {
522
- es.addEventListener(name, (e: MessageEvent) => {
523
- onEnvelope(parseData((e as MessageEvent).data));
524
- });
525
- }
526
-
527
- es.onerror = () => {
528
- if (sseCurrentSessionIdRef.current !== id) return;
529
- if (!sseAutoReconnectRef.current) {
530
- try {
531
- es.close();
532
- } catch {
533
- /* noop */
534
- }
535
- clineEsRef.current = null;
536
- return;
537
- }
538
- try {
539
- es.close();
540
- } catch {
541
- /* noop */
542
- }
543
- clineEsRef.current = null;
544
-
545
- const nextAttempt = sseReconnectAttemptRef.current + 1;
546
- sseReconnectAttemptRef.current = nextAttempt;
547
- // Backoff: 1s, 2s, 4s, 8s, … capped at 30s.
548
- const delayMs = Math.min(30_000, 1000 * 2 ** (nextAttempt - 1));
549
-
550
- if (sseReconnectTimerRef.current !== null) {
551
- clearTimeout(sseReconnectTimerRef.current);
552
- }
553
- sseReconnectTimerRef.current = window.setTimeout(() => {
554
- sseReconnectTimerRef.current = null;
555
- if (
556
- sseAutoReconnectRef.current &&
557
- sseCurrentSessionIdRef.current === id
558
- ) {
559
- connect();
560
- }
561
- }, delayMs);
562
- };
563
-
564
- es.onopen = () => {
565
- // Successful (re)connect — reset backoff.
566
- sseReconnectAttemptRef.current = 0;
567
- setClineError(null);
568
- setClineSuggestion(null);
569
- setClineErrorInfo(null);
570
- };
571
- };
572
-
573
- connect();
574
- },
575
- [appendClineMessage, markSeen, markSessionStreaming, setSessionDisplayState],
576
- );
577
-
578
- const closeClineSession = useCallback(() => {
579
- // Disable auto-reconnect first so the onerror handler short-circuits.
580
- sseAutoReconnectRef.current = false;
581
- sseCurrentSessionIdRef.current = null;
582
- if (sseReconnectTimerRef.current !== null) {
583
- clearTimeout(sseReconnectTimerRef.current);
584
- sseReconnectTimerRef.current = null;
585
- }
586
- if (clineEsRef.current) {
587
- try {
588
- clineEsRef.current.close();
589
- } catch {
590
- /* noop */
591
- }
592
- clineEsRef.current = null;
593
- }
594
- setClineMessages([]);
595
- setSeenClineMessages(new Set());
596
- setClineError(null);
597
- setClineSuggestion(null);
598
- setClineErrorInfo(null);
599
- setActiveClineSessionId(null);
600
- setActiveSource('bizar');
601
- }, []);
602
-
603
- const loadClineSession = useCallback(
604
- async (id: string) => {
605
- // Close any previous cline session cleanly.
606
- sseAutoReconnectRef.current = false;
607
- if (clineEsRef.current) {
608
- try {
609
- clineEsRef.current.close();
610
- } catch {
611
- /* noop */
612
- }
613
- clineEsRef.current = null;
614
- }
615
- if (sseReconnectTimerRef.current !== null) {
616
- clearTimeout(sseReconnectTimerRef.current);
617
- sseReconnectTimerRef.current = null;
618
- }
619
-
620
- setClineError(null);
621
- setClineSuggestion(null);
622
- setClineErrorInfo(null);
623
- setLoading(true);
624
-
625
- try {
626
- const data = await api.get<{ messages: ChatMessage[] }>(
627
- `/claude-sessions/${encodeURIComponent(id)}/messages`,
628
- );
629
- setClineMessages(data.messages || []);
630
- } catch (err) {
631
- const msg = (err as Error).message;
632
- // v5.0.0 — bug #4: surface the server's structured error
633
- // envelope so the UI can render a useful suggestion instead
634
- // of the raw upstream error string. The server returns
635
- // `{error, message, cause?, status?, suggestion?}` for every
636
- // non-2xx response (see serve-info.mjs + routes/cline-
637
- // session-detail.mjs).
638
- let info: ClineErrorInfo | null = null;
639
- if (err instanceof ApiError && err.data && typeof err.data === 'object') {
640
- const d = err.data as {
641
- error?: string;
642
- message?: string;
643
- cause?: string;
644
- status?: number;
645
- suggestion?: string;
646
- };
647
- // canRetry: 503/502 are transient (plugin offline /
648
- // upstream hiccup); the operator can fix the plugin and
649
- // try again. 400 is a hard bad_request — retrying with the
650
- // same payload won't help.
651
- const code = d.error || 'unknown';
652
- const canRetry =
653
- code !== 'bad_request' &&
654
- code !== 'too_many_subscribers' &&
655
- code !== 'unauthorized';
656
- info = {
657
- type: 'session_load_failed',
658
- code,
659
- message: d.message || msg,
660
- cause: d.cause,
661
- status: d.status,
662
- suggestion: d.suggestion ?? null,
663
- canRetry,
664
- };
665
- } else if (err instanceof ApiError) {
666
- info = {
667
- type: 'session_load_failed',
668
- code: 'unknown',
669
- message: msg,
670
- suggestion: null,
671
- canRetry: true,
672
- };
673
- }
674
- // Legacy fields — kept so the existing Chat.tsx thread-header
675
- // error display continues to work unchanged.
676
- setClineError(
677
- info
678
- ? `Cline session load failed (${info.code}): ${info.message}`
679
- : `Cline session load failed: ${msg}`,
680
- );
681
- setClineSuggestion(info?.suggestion ?? null);
682
- setClineErrorInfo(info);
683
- toastRef.current?.error(
684
- info
685
- ? `Cline session load failed: ${info.message}`
686
- : `Cline session load failed: ${msg}`,
687
- );
688
- setClineMessages([]);
689
- } finally {
690
- setLoading(false);
691
- }
692
-
693
- setActiveClineSessionId(id);
694
- setActiveSource('cline');
695
- markSessionStreaming(id, false);
696
- openSseForSession(id);
697
- },
698
- [markSessionStreaming, openSseForSession],
699
- );
700
-
701
- // ── Select a bizar session ──────────────────────────────────────────────────
702
- const selectBizarSession = useCallback(
703
- (sid: string) => {
704
- closeClineSession();
705
- setSessionId(sid);
706
- setActiveSource('bizar');
707
- loadChat(sid);
708
- // Reset unread for the session being opened.
709
- setSessionDisplayState(sid, { unread: 0 });
710
- },
711
- [closeClineSession, loadChat, setSessionDisplayState],
712
- );
713
-
714
- // ── Create session (cline primary; bizar fallback) ──────────────────────
715
- const onCreateSession = useCallback(async (): Promise<{
716
- ok: boolean;
717
- source?: 'bizar' | 'cline';
718
- id?: string;
719
- }> => {
720
- if (busy.create) return { ok: false };
721
- const agent = settings.defaultAgent || 'odin';
722
- setBusy((b) => ({ ...b, create: true }));
723
- try {
724
- // Try to create a real cline session first. This is the
725
- // primary create path: it gives the user a full conversation
726
- // roundtrip with SSE streaming, abort, etc.
727
- try {
728
- const created = await api.post<{
729
- id: string;
730
- title: string;
731
- agent: string;
732
- directory: string;
733
- createdAt: number;
734
- }>('/claude-sessions/new', { agent });
735
- await refreshClineSessions();
736
- // Open the newly created session right away so the user is
737
- // dropped into a fresh empty thread, not stuck on the
738
- // previous one.
739
- await loadClineSession(created.id);
740
- toastRef.current?.success(`Session ${created.id} created.`);
741
- return { ok: true, source: 'cline', id: created.id };
742
- } catch (clineErr) {
743
- // Cline plugin offline (503) — fall back to the local
744
- // per-project .jsonl session. Otherwise, surface the error.
745
- if (!(clineErr instanceof ApiError) || clineErr.status !== 503) {
746
- toastRef.current?.error(
747
- `Create failed: ${(clineErr as Error).message}`,
748
- );
749
- return { ok: false };
750
- }
751
- }
752
-
753
- // Fallback: local jsonl session.
754
- if (!snapshot.activeProject) {
755
- toastRef.current?.error(
756
- 'Pick a project in Overview to scope chat sessions.',
757
- );
758
- return { ok: false };
759
- }
760
- const created = await api.post<ChatSession>('/chat/sessions', {});
761
- setSessionId(created.id);
762
- setBizarMessages([]);
763
- setPinned(new Set());
764
- setSessionDisplayState(created.id, { state: 'idle', unread: 0 });
765
- await loadChat(created.id);
766
- refreshSessions().catch(() => {});
767
- toastRef.current?.success(`Session ${created.id} created.`);
768
- return { ok: true, source: 'bizar', id: created.id };
769
- } catch (err) {
770
- toastRef.current?.error(`Create failed: ${(err as Error).message}`);
771
- return { ok: false };
772
- } finally {
773
- setBusy((b) => ({ ...b, create: false }));
774
- }
775
- }, [
776
- busy.create,
777
- settings.defaultAgent,
778
- snapshot.activeProject,
779
- refreshClineSessions,
780
- loadClineSession,
781
- loadChat,
782
- refreshSessions,
783
- setSessionDisplayState,
784
- ]);
785
-
786
- // ── Send message ────────────────────────────────────────────────────────────
787
- const onSend = useCallback(
788
- async (
789
- message: string,
790
- agent: string,
791
- model: string,
792
- attachments?: string[],
793
- ): Promise<{ ok: boolean }> => {
794
- if (busy.send) return { ok: false };
795
- const trimmed = message.trim();
796
- if (!trimmed) return { ok: false };
797
-
798
- if (activeSource === 'cline' && activeClineSessionId) {
799
- const sid = activeClineSessionId;
800
- const optimisticId = `msg_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
801
- const optimistic: ChatMessage = {
802
- id: optimisticId,
803
- role: 'user',
804
- content: trimmed,
805
- agent,
806
- ts: new Date().toISOString(),
807
- };
808
- appendClineMessage(optimistic);
809
- markSeen(optimisticId);
810
- setBusy((b) => ({ ...b, send: true }));
811
- try {
812
- await api.post<{ ok: boolean; messageId: string }>(
813
- `/claude-sessions/${encodeURIComponent(sid)}/send`,
814
- {
815
- message: trimmed,
816
- agent,
817
- },
818
- );
819
- markSessionStreaming(sid, true);
820
- return { ok: true };
821
- } catch (err) {
822
- // Remove optimistic on failure.
823
- setClineMessages((cur) => cur.filter((m) => m.id !== optimisticId));
824
- toastRef.current?.error(
825
- `Send failed: ${(err as Error).message}`,
826
- );
827
- return { ok: false };
828
- } finally {
829
- setBusy((b) => ({ ...b, send: false }));
830
- }
831
- }
832
-
833
- // Bizar fallback.
834
- const optimistic: ChatMessage = {
835
- role: 'user',
836
- content: trimmed,
837
- agent,
838
- ts: new Date().toISOString(),
839
- };
840
- setBizarMessages((cur) => [...cur, optimistic]);
841
- setBusy((b) => ({ ...b, send: true }));
842
- markSessionStreaming(sessionId, true);
843
- try {
844
- const response = await api.post<{ messages?: ChatMessage[] }>(
845
- '/chat',
846
- {
847
- message: trimmed,
848
- agent,
849
- model,
850
- attachments: attachments || [],
851
- },
852
- );
853
- if (response?.messages) {
854
- for (const msg of response.messages) {
855
- if (msg.role === 'assistant') {
856
- setBizarMessages((cur) => {
857
- const exists = cur.some((m) => m.ts === msg.ts);
858
- return exists ? cur : [...cur, msg];
859
- });
860
- }
861
- }
862
- } else {
863
- toastRef.current?.info(
864
- 'Still processing… the response will appear shortly.',
865
- );
866
- }
867
- return { ok: true };
868
- } catch (err) {
869
- setBizarMessages((cur) => cur.filter((m) => m !== optimistic));
870
- toastRef.current?.error(
871
- `Send failed: ${(err as Error).message}`,
872
- );
873
- return { ok: false };
874
- } finally {
875
- setBusy((b) => ({ ...b, send: false }));
876
- markSessionStreaming(sessionId, false);
877
- }
878
- },
879
- [
880
- activeSource,
881
- activeClineSessionId,
882
- appendClineMessage,
883
- markSeen,
884
- markSessionStreaming,
885
- sessionId,
886
- busy.send,
887
- ],
888
- );
889
-
890
- const onRegenerate = useCallback(
891
- async (messageId: string) => {
892
- if (!sessionId) {
893
- toastRef.current?.error('No active session to regenerate.');
894
- return;
895
- }
896
- try {
897
- await api.post('/chat/regenerate', { sessionId, messageId });
898
- await loadChat(sessionId);
899
- } catch (err) {
900
- toastRef.current?.error(
901
- `Regenerate failed: ${(err as Error).message}`,
902
- );
903
- }
904
- },
905
- [sessionId, loadChat],
906
- );
907
-
908
- const deleteMessage = useCallback(
909
- (idx: number) => {
910
- if (activeSource === 'cline') {
911
- setClineMessages((cur) => cur.filter((_, i) => i !== idx));
912
- } else {
913
- setBizarMessages((cur) => cur.filter((_, i) => i !== idx));
914
- }
915
- },
916
- [activeSource],
917
- );
918
-
919
- const togglePin = useCallback((idx: number) => {
920
- setPinned((cur) => {
921
- const next = new Set(cur);
922
- if (next.has(idx)) next.delete(idx);
923
- else next.add(idx);
924
- return next;
925
- });
926
- }, []);
927
-
928
- const copyMessage = useCallback((m: ChatMessage) => {
929
- const text = m.content || m.message || '';
930
- try {
931
- navigator.clipboard?.writeText(text).then(
932
- () => toastRef.current?.success('Copied.'),
933
- () => toastRef.current?.error('Copy failed.'),
934
- );
935
- } catch {
936
- toastRef.current?.error('Copy failed.');
937
- }
938
- }, []);
939
-
940
- // ── Jump-to-latest ──────────────────────────────────────────────────────────
941
- const handleScroll = useCallback(() => {
942
- const el = listRef.current;
943
- if (!el) return;
944
- const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
945
- const atBottom = distFromBottom < 32;
946
- setStickToBottom(atBottom);
947
- if (atBottom) setNewMessageCount(0);
948
- }, []);
949
-
950
- const jumpToLatest = useCallback(() => {
951
- const el = listRef.current;
952
- if (!el) return;
953
- el.scrollTop = el.scrollHeight;
954
- setStickToBottom(true);
955
- setNewMessageCount(0);
956
- }, []);
957
-
958
- // ── Session rename / delete (rail row menu + info panel actions) ────────────
959
- const renameSession = useCallback(
960
- async (id: string, title: string): Promise<boolean> => {
961
- if (busy.rename) return false;
962
- setBusy((b) => ({ ...b, rename: true }));
963
- try {
964
- const trimmed = title.trim();
965
- if (!trimmed) {
966
- toastRef.current?.error('Title cannot be empty.');
967
- return false;
968
- }
969
- // Try the cline path first if this id is in the cline
970
- // session list, otherwise the local path. We dispatch by
971
- // inspecting the in-memory set (avoids a round trip).
972
- const isCline = clineSessions.some((s) => s.id === id);
973
- if (isCline) {
974
- await api.patch<{ id: string; title: string }>(
975
- `/claude-sessions/${encodeURIComponent(id)}`,
976
- { title: trimmed },
977
- );
978
- setClineSessions((cur) =>
979
- cur.map((s) => (s.id === id ? { ...s, title: trimmed } : s)),
980
- );
981
- } else {
982
- await api.post(
983
- `/chat/sessions/${encodeURIComponent(id)}/rename`,
984
- { title: trimmed },
985
- );
986
- setSessions((cur) =>
987
- cur.map((s) => (s.id === id ? { ...s, title: trimmed } : s)),
988
- );
989
- }
990
- toastRef.current?.success('Renamed.');
991
- return true;
992
- } catch (err) {
993
- toastRef.current?.error(
994
- `Rename failed: ${(err as Error).message}`,
995
- );
996
- return false;
997
- } finally {
998
- setBusy((b) => ({ ...b, rename: false }));
999
- }
1000
- },
1001
- [busy.rename, clineSessions],
1002
- );
1003
-
1004
- const deleteSession = useCallback(
1005
- async (id: string): Promise<boolean> => {
1006
- if (busy.delete) return false;
1007
- setBusy((b) => ({ ...b, delete: true }));
1008
- try {
1009
- const isCline = clineSessions.some((s) => s.id === id);
1010
- if (isCline) {
1011
- await api.del(`/claude-sessions/${encodeURIComponent(id)}`);
1012
- setClineSessions((cur) => cur.filter((s) => s.id !== id));
1013
- if (activeClineSessionId === id) {
1014
- closeClineSession();
1015
- }
1016
- } else {
1017
- await api.del(`/chat/sessions/${encodeURIComponent(id)}`);
1018
- setSessions((cur) => cur.filter((s) => s.id !== id));
1019
- if (sessionId === id) {
1020
- setSessionId('');
1021
- setBizarMessages([]);
1022
- }
1023
- }
1024
- setSessionStates((cur) => {
1025
- const next = { ...cur };
1026
- delete next[id];
1027
- return next;
1028
- });
1029
- toastRef.current?.success('Session deleted.');
1030
- return true;
1031
- } catch (err) {
1032
- toastRef.current?.error(
1033
- `Delete failed: ${(err as Error).message}`,
1034
- );
1035
- return false;
1036
- } finally {
1037
- setBusy((b) => ({ ...b, delete: false }));
1038
- }
1039
- },
1040
- [
1041
- busy.delete,
1042
- clineSessions,
1043
- activeClineSessionId,
1044
- closeClineSession,
1045
- sessionId,
1046
- ],
1047
- );
1048
-
1049
- // ── Auto-scroll on new messages ─────────────────────────────────────────────
1050
- useEffect(() => {
1051
- if (listRef.current) {
1052
- if (stickToBottom) {
1053
- listRef.current.scrollTop = listRef.current.scrollHeight;
1054
- } else {
1055
- setNewMessageCount((n) => n + 1);
1056
- }
1057
- }
1058
- // eslint-disable-next-line react-hooks/exhaustive-deps
1059
- }, [bizarMessages, clineMessages]);
1060
-
1061
- // ── Cleanup SSE on unmount ──────────────────────────────────────────────────
1062
- useEffect(() => {
1063
- return () => {
1064
- sseAutoReconnectRef.current = false;
1065
- sseCurrentSessionIdRef.current = null;
1066
- if (sseReconnectTimerRef.current !== null) {
1067
- clearTimeout(sseReconnectTimerRef.current);
1068
- sseReconnectTimerRef.current = null;
1069
- }
1070
- if (clineEsRef.current) {
1071
- try {
1072
- clineEsRef.current.close();
1073
- } catch {
1074
- /* noop */
1075
- }
1076
- clineEsRef.current = null;
1077
- }
1078
- };
1079
- }, []);
1080
-
1081
- // ── Initial load ────────────────────────────────────────────────────────────
1082
- useEffect(() => {
1083
- if (initialTaskId) {
1084
- loadTaskChat(initialTaskId);
1085
- } else {
1086
- loadChat();
1087
- }
1088
- refreshClineSessions();
1089
- // eslint-disable-next-line react-hooks/exhaustive-deps
1090
- }, []);
1091
-
1092
- // ── Listen for bizar:setChatTask events ────────────────────────────────────
1093
- useEffect(() => {
1094
- const handler = (e: Event) => {
1095
- const taskId = (e as CustomEvent<{ taskId: string }>).detail?.taskId;
1096
- if (taskId) loadTaskChat(taskId);
1097
- };
1098
- window.addEventListener('bizar:setChatTask', handler);
1099
- return () => window.removeEventListener('bizar:setChatTask', handler);
1100
- }, [loadTaskChat]);
1101
-
1102
- // ── WebSocket for live bizar updates ────────────────────────────────────────
1103
- useEffect(() => {
1104
- let closed = false;
1105
- import('../../lib/ws').then(({ Ws }) => {
1106
- if (closed) return;
1107
- const ws = new Ws();
1108
- ws.on((msg: { type: string; message?: ChatMessage }) => {
1109
- if (msg.type !== 'chat:message') return;
1110
- const next = msg.message;
1111
- if (!next) return;
1112
- setBizarMessages((cur) => {
1113
- const exists = cur.some((m) => m.ts === next.ts);
1114
- return exists ? cur : [...cur, next];
1115
- });
1116
- });
1117
- });
1118
- return () => {
1119
- closed = true;
1120
- };
1121
- }, []);
1122
-
1123
- return {
1124
- // ── State ────────────────────────────────────────────────────────────────
1125
- messages:
1126
- activeSource === 'cline' ? clineMessages : bizarMessages,
1127
- bizarMessages,
1128
- clineMessages,
1129
- sessions,
1130
- clineSessions,
1131
- sessionId,
1132
- setSessionId,
1133
- loading,
1134
- sending: busy.send,
1135
- pinned,
1136
- listRef,
1137
- // ── Cline state ──────────────────────────────────────────────────────
1138
- activeSource,
1139
- activeClineSessionId,
1140
- clineError,
1141
- clineSuggestion,
1142
- /**
1143
- * v5.0.0 — bug #4: structured error info consumed by
1144
- * `ChatInfoPanel`. `null` when no error is active.
1145
- */
1146
- clineErrorInfo,
1147
- /**
1148
- * v5.0.0 — bug #4: retry helper exposed for `ChatInfoPanel`'s
1149
- * Retry button. Calls `loadClineSession` on the currently
1150
- * active cline session (or no-op if none is active).
1151
- */
1152
- retryClineSession: () => {
1153
- if (activeClineSessionId) void loadClineSession(activeClineSessionId);
1154
- },
1155
- // ── v3.22 — per-session display state ──────────────────────────────────
1156
- sessionStates,
1157
- busy,
1158
- getSessionDisplay: (s: ChatSession) => ({
1159
- ...s,
1160
- state: sessionStates[s.id]?.state ?? 'idle',
1161
- unread: sessionStates[s.id]?.unread ?? 0,
1162
- pinned: sessionStates[s.id]?.pinned ?? false,
1163
- tree: sessionStates[s.id]?.tree,
1164
- }),
1165
- // ── Jump-to-latest ──────────────────────────────────────────────────────
1166
- stickToBottom,
1167
- newMessageCount,
1168
- handleScroll,
1169
- jumpToLatest,
1170
- // ── Toast integration ──────────────────────────────────────────────────
1171
- setToast,
1172
- // ── Actions ─────────────────────────────────────────────────────────────
1173
- loadChat,
1174
- loadTaskChat,
1175
- refreshSessions,
1176
- refreshClineSessions,
1177
- loadClineSession,
1178
- closeClineSession,
1179
- selectBizarSession,
1180
- onCreateSession,
1181
- onSend,
1182
- onRegenerate,
1183
- deleteMessage,
1184
- togglePin,
1185
- copyMessage,
1186
- renameSession,
1187
- deleteSession,
1188
- seenClineMessages,
1189
- };
1190
- }