@cueloop/client 0.1.0-alpha.10 → 0.1.0-alpha.100

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 (355) hide show
  1. package/native/build-pty.sh +51 -0
  2. package/native/build.sh +82 -0
  3. package/native/darwin-arm64/libcuelooppty.dylib +0 -0
  4. package/native/darwin-arm64/libcueloopvt.dylib +0 -0
  5. package/native/src/pty.zig +193 -0
  6. package/native/src/shim.zig +114 -0
  7. package/package.json +42 -34
  8. package/src/annotations/annotation-palette.ts +62 -0
  9. package/src/annotations/components/AnnotationCards.stories.tsx +86 -0
  10. package/src/annotations/components/AnnotationCards.test.tsx +42 -0
  11. package/src/annotations/components/AnnotationCards.tsx +364 -0
  12. package/src/annotations/components/DiscussionMarkerRail.stories.tsx +54 -0
  13. package/src/annotations/components/DiscussionMarkerRail.tsx +186 -0
  14. package/src/annotations/discussions.ts +77 -0
  15. package/src/annotations/thread-selection.test.ts +210 -0
  16. package/src/annotations/thread-selection.ts +242 -0
  17. package/src/annotations/use-annotation-surface.tsx +1238 -0
  18. package/src/app/App.appearance.test.tsx +53 -0
  19. package/src/{App.autoclose.test.tsx → app/App.autoclose.test.tsx} +77 -51
  20. package/src/app/App.deleted-scroll.test.tsx +187 -0
  21. package/src/app/App.diff.test.tsx +313 -0
  22. package/src/app/App.export.test.tsx +105 -0
  23. package/src/app/App.focus.test.ts +89 -0
  24. package/src/app/App.identity.test.tsx +84 -0
  25. package/src/app/App.plan-render.test.tsx +209 -0
  26. package/src/app/App.plan-review.test.tsx +428 -0
  27. package/src/app/App.prototype-markdown.test.tsx +128 -0
  28. package/src/app/App.prototype.test.tsx +176 -0
  29. package/src/app/App.readonly.test.tsx +210 -0
  30. package/src/app/App.regression.test.tsx +99 -0
  31. package/src/app/App.rendering.test.tsx +134 -0
  32. package/src/app/App.share-dialog.test.tsx +177 -0
  33. package/src/app/App.submit-confirm.test.tsx +267 -0
  34. package/src/app/App.test.tsx +496 -0
  35. package/src/app/App.thread-agent.test.ts +12 -0
  36. package/src/app/App.toast.test.tsx +124 -0
  37. package/src/app/App.tsx +1650 -0
  38. package/src/app/App.visual.test.tsx +59 -0
  39. package/src/app/App.walk.test.tsx +299 -0
  40. package/src/app/App.workbench.test.tsx +1149 -0
  41. package/src/app/app-key-state.ts +61 -0
  42. package/src/app/app-screens.tsx +606 -0
  43. package/src/app/app-view-model.ts +168 -0
  44. package/src/app/components/AppHeader.stories.tsx +50 -0
  45. package/src/app/components/AppHeader.tsx +81 -0
  46. package/src/app/components/AppShell.brand.test.tsx +58 -0
  47. package/src/app/components/AppShell.extensions.test.tsx +191 -0
  48. package/src/app/components/AppShell.stories.tsx +89 -0
  49. package/src/app/components/AppShell.tsx +358 -0
  50. package/src/app/components/AppShell.zoom.test.tsx +149 -0
  51. package/src/app/components/Breadcrumb.stories.tsx +19 -0
  52. package/src/app/components/Breadcrumb.tsx +45 -0
  53. package/src/app/components/MenuBar.stories.tsx +28 -0
  54. package/src/app/components/MenuBar.tsx +44 -0
  55. package/src/app/components/PaneGrid.stories.tsx +84 -0
  56. package/src/app/components/PaneGrid.tsx +128 -0
  57. package/src/app/components/PanelColumn.stories.tsx +25 -0
  58. package/src/app/components/PanelColumn.tsx +93 -0
  59. package/src/app/components/ShellHeader.stories.tsx +50 -0
  60. package/src/app/components/ShellHeader.tsx +137 -0
  61. package/src/app/components/WorkspacePanels.stories.tsx +36 -0
  62. package/src/app/components/WorkspacePanels.tsx +120 -0
  63. package/src/app/components/pages/Workspace.stories.tsx +181 -0
  64. package/src/app/intent-dispatch.test.ts +728 -0
  65. package/src/app/intent-dispatch.ts +687 -0
  66. package/src/app/launch-layout.test.ts +78 -0
  67. package/src/app/launch-layout.ts +57 -0
  68. package/src/app/ready-signal.test.ts +85 -0
  69. package/src/app/ready-signal.ts +52 -0
  70. package/src/app/run.ts +109 -0
  71. package/src/{serve.test.ts → app/serve.test.ts} +24 -12
  72. package/src/app/serve.ts +111 -0
  73. package/src/app/version.ts +5 -0
  74. package/src/appearance/components/theme-context.stories.tsx +46 -0
  75. package/src/appearance/components/theme-context.tsx +37 -0
  76. package/src/appearance/theme-presets.test.ts +51 -0
  77. package/src/appearance/theme-presets.ts +219 -0
  78. package/src/appearance/theme.ts +112 -0
  79. package/src/appearance/visual/contrast.test.ts +35 -0
  80. package/src/appearance/visual/contrast.ts +33 -0
  81. package/src/diff/components/AnnotatableFileView.stories.tsx +42 -0
  82. package/src/diff/components/AnnotatableFileView.test.tsx +157 -0
  83. package/src/diff/components/AnnotatableFileView.tsx +141 -0
  84. package/src/diff/components/BareWorkbenchFileView.stories.tsx +27 -0
  85. package/src/diff/components/BareWorkbenchFileView.tsx +63 -0
  86. package/src/diff/components/DiffContentView.scroll.test.tsx +434 -0
  87. package/src/diff/components/DiffContentView.split.test.tsx +140 -0
  88. package/src/diff/components/DiffContentView.stories.tsx +80 -0
  89. package/src/diff/components/DiffContentView.syntax.test.tsx +82 -0
  90. package/src/diff/components/DiffContentView.tsx +1055 -0
  91. package/src/diff/components/WalkWizard.stories.tsx +100 -0
  92. package/src/diff/components/WalkWizard.tsx +146 -0
  93. package/src/diff/components/diff-content-view-layout.test.ts +198 -0
  94. package/src/diff/components/diff-content-view-layout.ts +84 -0
  95. package/src/diff/diff-fold.test.ts +78 -0
  96. package/src/diff/diff-fold.ts +126 -0
  97. package/src/diff/diff-hunk-curate.test.ts +315 -0
  98. package/src/diff/diff-hunk-curate.ts +152 -0
  99. package/src/diff/diff-intraline.test.ts +239 -0
  100. package/src/diff/diff-intraline.ts +238 -0
  101. package/src/diff/diff-syntax.test.ts +125 -0
  102. package/src/diff/diff-syntax.ts +186 -0
  103. package/src/diff/split-diff.test.ts +110 -0
  104. package/src/diff/split-diff.ts +144 -0
  105. package/src/diff/view-diff.test.ts +325 -0
  106. package/src/diff/view-diff.ts +522 -0
  107. package/src/diff/walk.test.ts +121 -0
  108. package/src/diff/walk.ts +73 -0
  109. package/src/index.ts +31 -2
  110. package/src/integrations/attribution.test.ts +31 -0
  111. package/src/integrations/attribution.ts +42 -0
  112. package/src/integrations/client-extension-registry.test.tsx +61 -0
  113. package/src/integrations/client-extension-registry.ts +159 -0
  114. package/src/integrations/clipboard.test.ts +23 -0
  115. package/src/integrations/clipboard.ts +33 -0
  116. package/src/integrations/components/ExtensionZone.stories.tsx +28 -0
  117. package/src/integrations/components/ExtensionZone.tsx +145 -0
  118. package/src/integrations/components/ShareDialog.stories.tsx +121 -0
  119. package/src/integrations/components/ShareDialog.tsx +550 -0
  120. package/src/integrations/components/share-dialog-store.ts +55 -0
  121. package/src/integrations/editor.test.ts +149 -0
  122. package/src/integrations/editor.ts +160 -0
  123. package/src/integrations/github-identity.test.ts +58 -0
  124. package/src/integrations/github-identity.ts +41 -0
  125. package/src/integrations/herdr.test.ts +43 -0
  126. package/src/integrations/herdr.ts +38 -0
  127. package/src/integrations/integrations.test.ts +41 -0
  128. package/src/integrations/integrations.ts +32 -0
  129. package/src/integrations/pasted-image.test.ts +67 -0
  130. package/src/integrations/pasted-image.ts +60 -0
  131. package/src/integrations/share.ts +238 -0
  132. package/src/keyboard/components/CompletionOverlay.stories.tsx +37 -0
  133. package/src/keyboard/components/CompletionOverlay.tsx +75 -0
  134. package/src/keyboard/components/HandleChipsInput.stories.tsx +24 -0
  135. package/src/keyboard/components/HandleChipsInput.test.tsx +35 -0
  136. package/src/keyboard/components/HandleChipsInput.tsx +143 -0
  137. package/src/keyboard/components/KeybindsDialog.stories.tsx +31 -0
  138. package/src/keyboard/components/KeybindsDialog.tsx +103 -0
  139. package/src/keyboard/components/NavModeHint.stories.tsx +40 -0
  140. package/src/keyboard/components/NavModeHint.tsx +31 -0
  141. package/src/keyboard/components/SlashComposer.stories.tsx +37 -0
  142. package/src/keyboard/components/SlashComposer.tsx +87 -0
  143. package/src/keyboard/key-bindings.test.ts +156 -0
  144. package/src/keyboard/key-bindings.ts +459 -0
  145. package/src/keyboard/keymap.test.ts +611 -0
  146. package/src/keyboard/keymap.ts +395 -0
  147. package/src/keyboard/skills.test.ts +74 -0
  148. package/src/keyboard/skills.ts +75 -0
  149. package/src/keyboard/slash-palette.test.ts +103 -0
  150. package/src/keyboard/slash-palette.ts +129 -0
  151. package/src/keyboard/thread-chords.test.ts +79 -0
  152. package/src/keyboard/thread-chords.ts +163 -0
  153. package/src/keyboard/use-shared-keyboard.ts +55 -0
  154. package/src/markdown/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  155. package/src/markdown/components/MarkdownGridBlock.stories.tsx +33 -0
  156. package/src/markdown/components/MarkdownGridBlock.tsx +72 -0
  157. package/src/markdown/components/MarkdownThreadEditor.stories.tsx +15 -0
  158. package/src/markdown/components/MarkdownThreadEditor.test.tsx +98 -0
  159. package/src/markdown/components/MarkdownThreadEditor.tsx +160 -0
  160. package/src/markdown/components/ThreadView.selection.test.tsx +951 -0
  161. package/src/markdown/components/ThreadView.slash.test.tsx +146 -0
  162. package/src/markdown/components/ThreadView.stories.tsx +26 -0
  163. package/src/markdown/components/ThreadView.tsx +560 -0
  164. package/src/markdown/mark-runs.ts +121 -0
  165. package/src/markdown/markdown-editor-syntax-style.ts +50 -0
  166. package/src/markdown/markdown-grid.test.ts +101 -0
  167. package/src/markdown/markdown-grid.ts +203 -0
  168. package/src/markdown/markdown-highlight.test.ts +61 -0
  169. package/src/markdown/markdown-highlight.ts +124 -0
  170. package/src/markdown/thread-code-syntax.ts +45 -0
  171. package/src/markdown/use-thread-body-editing.tsx +54 -0
  172. package/src/markdown/view-plan.regression.test.ts +60 -0
  173. package/src/markdown/view-plan.test.ts +545 -0
  174. package/src/markdown/view-plan.ts +1009 -0
  175. package/src/performance/otlp-trace.test.ts +119 -0
  176. package/src/performance/otlp-trace.ts +134 -0
  177. package/src/performance/perf-report.ts +18 -0
  178. package/src/performance/perf-timings.test.ts +18 -0
  179. package/src/performance/perf-timings.ts +39 -0
  180. package/src/prototype/components/PrototypeContentView.stories.tsx +19 -0
  181. package/src/prototype/components/PrototypeContentView.tsx +442 -0
  182. package/src/prototype/prototype-browser.test.ts +81 -0
  183. package/src/prototype/prototype-browser.ts +321 -0
  184. package/src/prototype/prototype-pixels.tsx +35 -0
  185. package/src/settings/components/SettingsDialog.stories.tsx +117 -0
  186. package/src/settings/components/SettingsDialog.tsx +183 -0
  187. package/src/settings/components/SettingsRows.stories.tsx +18 -0
  188. package/src/settings/components/SettingsRows.tsx +112 -0
  189. package/src/settings/components/quick-actions-editor.stories.tsx +58 -0
  190. package/src/settings/components/quick-actions-editor.test.tsx +120 -0
  191. package/src/settings/components/quick-actions-editor.tsx +222 -0
  192. package/src/settings/config-identity.test.ts +78 -0
  193. package/src/settings/config.test.ts +676 -0
  194. package/src/settings/config.ts +628 -0
  195. package/src/settings/use-settings-dialog.tsx +351 -0
  196. package/src/stories/__snapshots__/stories.test.tsx.snap +2358 -0
  197. package/src/stories/stories-app.tsx +196 -0
  198. package/src/stories/stories.test.tsx +74 -0
  199. package/src/stories/story-fixtures.ts +102 -0
  200. package/src/stories/story-tree.test.ts +108 -0
  201. package/src/stories/story.ts +154 -0
  202. package/src/terminal/ansi-screen.test.ts +28 -0
  203. package/src/terminal/ansi-screen.ts +72 -0
  204. package/src/terminal/components/AnsiScreen.stories.tsx +12 -0
  205. package/src/terminal/components/AnsiScreen.tsx +28 -0
  206. package/src/terminal/components/terminal-pane.test.tsx +40 -0
  207. package/src/terminal/components/terminal-pane.ts +196 -0
  208. package/src/terminal/ghostty-terminal.test.ts +85 -0
  209. package/src/terminal/ghostty-terminal.ts +197 -0
  210. package/src/terminal/kitty-image.test.ts +66 -0
  211. package/src/terminal/kitty-image.ts +137 -0
  212. package/src/terminal/pty.test.ts +81 -0
  213. package/src/terminal/pty.ts +246 -0
  214. package/src/testing/test-support.test.ts +264 -0
  215. package/src/testing/test-support.ts +367 -0
  216. package/src/thread/agent-continuation.test.ts +32 -0
  217. package/src/thread/agent-continuation.ts +13 -0
  218. package/src/thread/agent-transcript.test.ts +161 -0
  219. package/src/thread/agent-transcript.ts +336 -0
  220. package/src/thread/components/AgentConfigControls.stories.tsx +39 -0
  221. package/src/thread/components/AgentConfigControls.test.tsx +112 -0
  222. package/src/thread/components/AgentConfigControls.tsx +114 -0
  223. package/src/thread/components/AgentThreadPane.stories.tsx +105 -0
  224. package/src/thread/components/AgentThreadPane.test.tsx +829 -0
  225. package/src/thread/components/AgentThreadPane.tsx +367 -0
  226. package/src/thread/components/ThreadFooter.stories.tsx +30 -0
  227. package/src/thread/components/ThreadFooter.test.tsx +49 -0
  228. package/src/thread/components/ThreadFooter.tsx +77 -0
  229. package/src/thread/components/ThreadTree.stories.tsx +46 -0
  230. package/src/thread/components/ThreadTree.test.tsx +41 -0
  231. package/src/thread/components/ThreadTree.tsx +267 -0
  232. package/src/thread/components/thread-tree.test.ts +137 -0
  233. package/src/thread/components/thread-tree.ts +112 -0
  234. package/src/thread/thread-controller.curate.test.ts +502 -0
  235. package/src/thread/thread-controller.derivation.test.ts +62 -0
  236. package/src/thread/thread-controller.live-diff.test.ts +357 -0
  237. package/src/thread/thread-controller.optimistic.test.ts +69 -0
  238. package/src/thread/thread-controller.share.test.ts +573 -0
  239. package/src/thread/thread-controller.switch-cache.test.ts +174 -0
  240. package/src/thread/thread-controller.tree.test.ts +263 -0
  241. package/src/thread/thread-controller.ts +2018 -0
  242. package/src/thread/tree-view.test.ts +128 -0
  243. package/src/thread/tree-view.ts +146 -0
  244. package/src/thread/use-thread-agent.test.tsx +208 -0
  245. package/src/thread/use-thread-agent.ts +111 -0
  246. package/src/ui/components/CodeBlock.stories.tsx +42 -0
  247. package/src/ui/components/CodeBlock.tsx +80 -0
  248. package/src/ui/components/ConfirmCard.stories.tsx +37 -0
  249. package/src/ui/components/ConfirmCard.tsx +151 -0
  250. package/src/ui/components/ConfirmDialog.stories.tsx +21 -0
  251. package/src/ui/components/ConfirmDialog.tsx +72 -0
  252. package/src/ui/components/OverlayScrollbar.stories.tsx +33 -0
  253. package/src/ui/components/OverlayScrollbar.test.tsx +139 -0
  254. package/src/ui/components/OverlayScrollbar.tsx +129 -0
  255. package/src/ui/components/PromptDialog.stories.tsx +36 -0
  256. package/src/ui/components/PromptDialog.test.tsx +65 -0
  257. package/src/ui/components/PromptDialog.tsx +99 -0
  258. package/src/ui/components/RootOverlay.stories.tsx +45 -0
  259. package/src/ui/components/RootOverlay.tsx +49 -0
  260. package/src/ui/components/ScrollArea.stories.tsx +20 -0
  261. package/src/ui/components/ScrollArea.tsx +61 -0
  262. package/src/ui/components/Toast.stories.tsx +19 -0
  263. package/src/ui/components/Toast.tsx +54 -0
  264. package/src/ui/components/Tooltip.stories.tsx +26 -0
  265. package/src/ui/components/Tooltip.tsx +72 -0
  266. package/src/ui/components/menu-control.stories.tsx +35 -0
  267. package/src/ui/components/menu-control.tsx +43 -0
  268. package/src/ui/components/primitives/Button.stories.tsx +32 -0
  269. package/src/ui/components/primitives/Button.tsx +57 -0
  270. package/src/ui/components/primitives/Card.stories.tsx +25 -0
  271. package/src/ui/components/primitives/Card.tsx +62 -0
  272. package/src/ui/components/primitives/Dialog.stories.tsx +24 -0
  273. package/src/ui/components/primitives/Dialog.test.tsx +38 -0
  274. package/src/ui/components/primitives/Dialog.tsx +69 -0
  275. package/src/ui/components/primitives/DialogActions.stories.tsx +20 -0
  276. package/src/ui/components/primitives/DialogActions.tsx +39 -0
  277. package/src/ui/components/primitives/IconButton.stories.tsx +36 -0
  278. package/src/ui/components/primitives/IconButton.tsx +58 -0
  279. package/src/ui/components/primitives/StatusBar.stories.tsx +11 -0
  280. package/src/ui/components/primitives/StatusBar.tsx +23 -0
  281. package/src/ui/components/primitives/Switch.stories.tsx +17 -0
  282. package/src/ui/components/primitives/Switch.tsx +32 -0
  283. package/src/ui/components/primitives/Tabs.stories.tsx +29 -0
  284. package/src/ui/components/primitives/Tabs.tsx +101 -0
  285. package/src/ui/components/primitives/Toolbar.stories.tsx +17 -0
  286. package/src/ui/components/primitives/Toolbar.tsx +14 -0
  287. package/src/ui/components/primitives/Tree.stories.tsx +124 -0
  288. package/src/ui/components/primitives/Tree.tsx +133 -0
  289. package/src/ui/components/primitives/frame.test.ts +12 -0
  290. package/src/ui/components/primitives/frame.ts +17 -0
  291. package/src/ui/components/primitives/icons.ts +61 -0
  292. package/src/ui/components/primitives/tree-model.test.ts +99 -0
  293. package/src/ui/components/primitives/tree-model.ts +137 -0
  294. package/src/ui/components/syntax-highlight.ts +133 -0
  295. package/src/ui/components/truncate-text.ts +7 -0
  296. package/src/ui/components/truncate-title.test.ts +31 -0
  297. package/src/ui/components/truncate-title.ts +15 -0
  298. package/src/ui/use-frame-measure.ts +74 -0
  299. package/src/ui/use-terminal-virtualizer.ts +259 -0
  300. package/src/workbench/components/ChangesColumn.stories.tsx +44 -0
  301. package/src/workbench/components/ChangesColumn.tsx +203 -0
  302. package/src/workbench/components/ChangesFileTree.test.tsx +181 -0
  303. package/src/workbench/components/EditorGrid.split.test.tsx +118 -0
  304. package/src/workbench/components/EditorGrid.stories.tsx +86 -0
  305. package/src/workbench/components/EditorGrid.tabs.test.tsx +118 -0
  306. package/src/workbench/components/EditorGrid.tsx +544 -0
  307. package/src/workbench/components/EditorGrid.zoom.test.tsx +132 -0
  308. package/src/workbench/components/FileContentsView.stories.tsx +16 -0
  309. package/src/workbench/components/FileContentsView.test.tsx +29 -0
  310. package/src/workbench/components/FileContentsView.tsx +100 -0
  311. package/src/workbench/components/GridTabContent.actions.test.tsx +151 -0
  312. package/src/workbench/components/GridTabContent.stories.tsx +59 -0
  313. package/src/workbench/components/GridTabContent.tsx +193 -0
  314. package/src/workbench/components/ProjectTreeView.stories.tsx +25 -0
  315. package/src/workbench/components/ProjectTreeView.test.tsx +236 -0
  316. package/src/workbench/components/ProjectTreeView.tsx +151 -0
  317. package/src/workbench/components/TreePane.stories.tsx +106 -0
  318. package/src/workbench/components/TreePane.tsx +155 -0
  319. package/src/workbench/components/WelcomePlayground.stories.tsx +12 -0
  320. package/src/workbench/components/WelcomePlayground.test.tsx +83 -0
  321. package/src/workbench/components/WelcomePlayground.tsx +138 -0
  322. package/src/workbench/components/editor-grid.test.ts +184 -0
  323. package/src/workbench/components/editor-grid.ts +281 -0
  324. package/src/workbench/components/file-tree.test.ts +60 -0
  325. package/src/workbench/components/file-tree.ts +112 -0
  326. package/src/workbench/use-changes-workbench.ts +205 -0
  327. package/src/workbench/use-remember-layout.ts +31 -0
  328. package/src/workbench/use-repo-changes.ts +91 -0
  329. package/src/workbench/workbench-snapshot.test.ts +99 -0
  330. package/src/workbench/workbench-snapshot.ts +59 -0
  331. package/src/App.diff.test.tsx +0 -99
  332. package/src/App.export.test.tsx +0 -88
  333. package/src/App.plan-review.test.tsx +0 -259
  334. package/src/App.readonly.test.tsx +0 -148
  335. package/src/App.rendering.test.tsx +0 -108
  336. package/src/App.submit-confirm.test.tsx +0 -183
  337. package/src/App.test.tsx +0 -218
  338. package/src/App.tsx +0 -1249
  339. package/src/config.test.ts +0 -116
  340. package/src/config.ts +0 -150
  341. package/src/editor.ts +0 -34
  342. package/src/herdr.test.ts +0 -68
  343. package/src/herdr.ts +0 -34
  344. package/src/keymap.test.ts +0 -270
  345. package/src/keymap.ts +0 -173
  346. package/src/run.ts +0 -30
  347. package/src/serve.ts +0 -75
  348. package/src/session-controller.ts +0 -398
  349. package/src/syntax.test.ts +0 -41
  350. package/src/syntax.ts +0 -88
  351. package/src/theme.ts +0 -46
  352. package/src/view-diff.test.ts +0 -55
  353. package/src/view-diff.ts +0 -86
  354. package/src/view.test.ts +0 -157
  355. package/src/view.ts +0 -364
@@ -0,0 +1,168 @@
1
+ import type { Dispatch, MutableRefObject, SetStateAction } from "react";
2
+ import type { Thread, MessageOutcome } from "@cueloop/schema";
3
+ import type { Mode } from "./intent-dispatch";
4
+ import type { Intent, KeyState } from "../keyboard/keymap";
5
+ import type { Completion } from "../thread/thread-controller";
6
+ import type { WalkFile } from "../diff/walk";
7
+ import type { QuickAction } from "../settings/config";
8
+ import { viewedCount } from "../diff/walk";
9
+ import type { ConfirmCardProps } from "../ui/components/ConfirmCard";
10
+ import type { BreadcrumbItem } from "./components/Breadcrumb";
11
+
12
+ export function computeRoleCapabilities(
13
+ readOnly: boolean,
14
+ role: "owner" | "observer" | "collaborator",
15
+ ) {
16
+ const observer = readOnly || role === "observer";
17
+
18
+ return { observer, isOwner: !observer && role === "owner" };
19
+ }
20
+
21
+ export function deriveReviewFlags(session: Thread | null) {
22
+ return {
23
+ isDiff: session?.artifact.type === "diff",
24
+ isPrototype: session?.artifact.type === "prototype",
25
+ resolved: session?.status === "resolved",
26
+ };
27
+ }
28
+
29
+ export function isWalking(isDiff: boolean, walk: { index: number } | null): boolean {
30
+ return isDiff && walk !== null;
31
+ }
32
+
33
+ /**
34
+ * A prototype renders as a kitty pixel mockup only in the opt-in experimental mode:
35
+ * the flag is on and the artifact carries an HTML entry. Otherwise it is the default
36
+ * markdown design doc, rendered through the thread/markdown path.
37
+ */
38
+ export function isPixelPrototypeMode(
39
+ isPrototype: boolean,
40
+ prototypePixels: boolean,
41
+ prototypePath: string | undefined,
42
+ ): boolean {
43
+ return isPrototype && prototypePixels && Boolean(prototypePath);
44
+ }
45
+
46
+ export function resolveOverlay(
47
+ mode: Mode,
48
+ completionPhase: Completion["phase"],
49
+ walking: boolean,
50
+ ): KeyState["overlay"] {
51
+ if (mode.type === "compose" || mode.type === "railEdit") return "compose";
52
+ if (mode.type === "submit") return "submit";
53
+ if (mode.type === "confirmDelete") return "confirm";
54
+ if (
55
+ mode.type === "rename" ||
56
+ mode.type === "renameThread" ||
57
+ mode.type === "nameSelf" ||
58
+ mode.type === "treePrompt"
59
+ )
60
+ return "prompt";
61
+ if (mode.type === "spanActions") return "spanActions";
62
+ if (completionPhase === "prompt") return "completion-prompt";
63
+ if (completionPhase === "counting") return "completion-counting";
64
+ if (walking) return "walk";
65
+
66
+ return "none";
67
+ }
68
+
69
+ export function isCompletionOverlayPhase(
70
+ completion: Completion,
71
+ ): completion is { phase: "prompt" } | { phase: "counting"; remaining: number } {
72
+ return completion.phase === "prompt" || completion.phase === "counting";
73
+ }
74
+
75
+ export function buildHeaderItems(params: {
76
+ session: Thread;
77
+ resolved: boolean;
78
+ observer: boolean;
79
+ role: "owner" | "observer" | "collaborator";
80
+ }): BreadcrumbItem[] {
81
+ const { session, resolved, observer, role } = params;
82
+
83
+ return [
84
+ { label: "cueloop", tone: "accent" },
85
+ ...(resolved
86
+ ? [
87
+ {
88
+ label: `resolved: ${session.message!.outcome.replace("_", " ")}`,
89
+ tone: "green" as const,
90
+ },
91
+ ]
92
+ : []),
93
+ ...(observer ? [{ label: "observer", tone: "dim" as const }] : []),
94
+ ...(role === "collaborator"
95
+ ? [{ label: "shared · your notes save as you go", tone: "dim" as const }]
96
+ : []),
97
+ {
98
+ label: `${session.artifact.meta.title ?? session.artifact.meta.planPath ?? session.id} · rev ${session.revisions.length}`,
99
+ tone: "dim",
100
+ },
101
+ { label: `submitted by ${session.artifact.meta.agent ?? "unknown"}`, tone: "dim" },
102
+ ];
103
+ }
104
+
105
+ export function buildRenderFlags(params: {
106
+ session: Thread;
107
+ isOwner: boolean;
108
+ isDiff: boolean;
109
+ isPixelPrototype: boolean;
110
+ resolved: boolean;
111
+ menuDialog: "keybinds" | "settings" | null;
112
+ resolvedIds: Set<string>;
113
+ }) {
114
+ const { session, isOwner, isDiff, isPixelPrototype, resolved, menuDialog, resolvedIds } = params;
115
+
116
+ return {
117
+ showOwnerActions: isOwner && !isDiff && !resolved,
118
+ showDiffRefresh:
119
+ !resolved &&
120
+ (session.artifact.meta.prRefreshHeadSha !== undefined ||
121
+ (session.artifact.type === "diff" &&
122
+ session.artifact.meta.vcsChangeId !== undefined &&
123
+ session.artifact.meta.workbench !== true)),
124
+ prototypeCanComment: isOwner && !resolved,
125
+ chromeHidden: menuDialog !== null,
126
+ prototypePath: session.artifact.meta.prototypePath ?? "",
127
+ // a markdown prototype interleaves resolved cards like a plan; only the pixel mockup opts out
128
+ railResolvedIds: isDiff || isPixelPrototype ? null : resolvedIds,
129
+ };
130
+ }
131
+
132
+ interface DraftHandlerDeps {
133
+ liveInput: MutableRefObject<string>;
134
+ setMode: Dispatch<SetStateAction<Mode>>;
135
+ dispatch: (intent: Intent) => void;
136
+ }
137
+
138
+ export function buildSubmitConfirmState(
139
+ deps: DraftHandlerDeps & {
140
+ mode: Mode;
141
+ isDiff: boolean;
142
+ session: Thread;
143
+ walkFileList: WalkFile[];
144
+ viewedPaths: Set<string>;
145
+ quickActions: QuickAction[];
146
+ },
147
+ ): Omit<ConfirmCardProps, "theme"> | null {
148
+ const { mode, isDiff, session, walkFileList, viewedPaths, liveInput, setMode, dispatch } = deps;
149
+
150
+ if (mode.type !== "submit") return null;
151
+
152
+ return {
153
+ message: mode.message,
154
+ summary: mode.summary,
155
+ quickActions: deps.quickActions,
156
+ viewedSummary:
157
+ isDiff && session.viewedPaths !== undefined
158
+ ? `${viewedCount(walkFileList, viewedPaths)}/${walkFileList.length} files viewed`
159
+ : undefined,
160
+ onInput: (summary: string) => {
161
+ liveInput.current = summary;
162
+ setMode({ ...mode, summary });
163
+ },
164
+ onSelectMessage: (message: MessageOutcome) => setMode({ ...mode, message }),
165
+ onSubmit: () => dispatch({ type: "submitMessage" }),
166
+ onCancel: () => dispatch({ type: "closeOverlay" }),
167
+ };
168
+ }
@@ -0,0 +1,50 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../stories/story";
4
+ import { AppHeader } from "./AppHeader";
5
+ import { Button } from "../../ui/components/primitives/Button";
6
+ import { Toolbar } from "../../ui/components/primitives/Toolbar";
7
+
8
+ export const meta: StoryMeta = { title: "Chrome/AppHeader" };
9
+
10
+ export const NoThread: Story = {
11
+ render: () => (
12
+ <AppHeader
13
+ onOpenMenu={() => {}}
14
+ sidebarOpen
15
+ onToggleSidebar={() => {}}
16
+ title=""
17
+ changesOpen={false}
18
+ onToggleChanges={() => {}}
19
+ theme={DARK}
20
+ />
21
+ ),
22
+ expectedColors: [DARK.accent],
23
+ size: { width: 80, height: 3 },
24
+ };
25
+
26
+ export const ThreadWithActions: Story = {
27
+ render: () => (
28
+ <AppHeader
29
+ onOpenMenu={() => {}}
30
+ sidebarOpen
31
+ onToggleSidebar={() => {}}
32
+ title="Review the accent change"
33
+ editShare={
34
+ <Toolbar>
35
+ <Button onPress={() => {}} theme={DARK}>
36
+ {" Edit "}
37
+ </Button>
38
+ <Button onPress={() => {}} theme={DARK}>
39
+ {" Share "}
40
+ </Button>
41
+ </Toolbar>
42
+ }
43
+ changesOpen
44
+ onToggleChanges={() => {}}
45
+ theme={DARK}
46
+ />
47
+ ),
48
+ expectedColors: [DARK.accent],
49
+ size: { width: 80, height: 3 },
50
+ };
@@ -0,0 +1,81 @@
1
+ // The one workbench header, split into column-aligned segments so it reads the
2
+ // same for every primitive: gear + left toggle over the Threads sidebar, the
3
+ // current thread's title (mirroring the sidebar selection) plus the owner's
4
+ // Edit/Share over the thread column, and a Changes label + right toggle over the
5
+ // right column. Collapsing a column keeps its toggle reachable at the edge.
6
+
7
+ import React from "react";
8
+ import type { Theme } from "../../appearance/theme";
9
+ import { IconButton } from "../../ui/components/primitives/IconButton";
10
+ import { NERD } from "../../ui/components/primitives/icons";
11
+
12
+ export interface AppHeaderProps {
13
+ onOpenMenu: () => void;
14
+ sidebarOpen: boolean;
15
+ sidebarWidth?: number;
16
+ onToggleSidebar: () => void;
17
+ title: string;
18
+ editShare?: React.ReactNode;
19
+ changesOpen: boolean;
20
+ changesWidth?: number;
21
+ onToggleChanges: () => void;
22
+ theme?: Theme;
23
+ }
24
+
25
+ export function AppHeader({
26
+ onOpenMenu,
27
+ sidebarOpen,
28
+ sidebarWidth = 30,
29
+ onToggleSidebar,
30
+ title,
31
+ editShare,
32
+ changesOpen,
33
+ changesWidth = 32,
34
+ onToggleChanges,
35
+ theme,
36
+ }: AppHeaderProps): React.ReactNode {
37
+ return (
38
+ <box style={{ flexDirection: "row", height: 2, paddingTop: 1, backgroundColor: theme?.panel }}>
39
+ {/* over the Threads sidebar: settings gear + the left toggle, product mark */}
40
+ <box
41
+ style={{
42
+ flexDirection: "row",
43
+ width: sidebarOpen ? sidebarWidth : undefined,
44
+ }}
45
+ >
46
+ <box onMouseUp={onOpenMenu} style={{ paddingRight: 2 }}>
47
+ <text fg={theme?.textMuted}>{NERD.settings}</text>
48
+ </box>
49
+ <IconButton
50
+ glyph={sidebarOpen ? NERD.sidebarLeft : NERD.sidebarLeftOff}
51
+ onPress={onToggleSidebar}
52
+ marginRight={2}
53
+ theme={theme}
54
+ />
55
+ <text fg={theme?.accent}>cueloop</text>
56
+ </box>
57
+ {/* over the thread column: the title, then the owner's Edit/Share at its right edge */}
58
+ <box style={{ flexDirection: "row", flexGrow: 1, paddingLeft: 1, paddingRight: 1 }}>
59
+ <text fg={theme?.textDim}>{title}</text>
60
+ <box style={{ flexGrow: 1 }} />
61
+ {editShare}
62
+ </box>
63
+ {/* over the right column: the Changes label when open, the right toggle always at the edge */}
64
+ <box
65
+ style={{
66
+ flexDirection: "row",
67
+ paddingRight: 1,
68
+ width: changesOpen ? changesWidth : undefined,
69
+ }}
70
+ >
71
+ {changesOpen ? <text fg={theme?.textDim}>Changes</text> : null}
72
+ <box style={{ flexGrow: 1 }} />
73
+ <IconButton
74
+ glyph={changesOpen ? NERD.sidebarRight : NERD.sidebarRightOff}
75
+ onPress={onToggleChanges}
76
+ theme={theme}
77
+ />
78
+ </box>
79
+ </box>
80
+ );
81
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The collapsed-sidebar brand row keeps the gear, the Threads toggle, and the
3
+ * "cueloop" mark on one line. A long thread title used to shrink the brand box
4
+ * until "cueloop" and the toggle wrapped onto the header's underline row; the
5
+ * title truncates instead, so the brand never shrinks.
6
+ */
7
+
8
+ import { describe, expect, test } from "bun:test";
9
+ import { testRender } from "@opentui/react/test-utils";
10
+ import React from "react";
11
+ import { AppShell } from "./AppShell";
12
+ import { NERD } from "../../ui/components/primitives/icons";
13
+ import { settle } from "../../testing/test-support";
14
+
15
+ const LONG_TITLE = "working tree @ GRO-1981-fe-billing-promo-cookie-migration";
16
+
17
+ async function brandFrame(): Promise<string[]> {
18
+ const setup = await testRender(
19
+ <AppShell
20
+ sidebarOpen={false}
21
+ onToggleSidebar={() => {}}
22
+ onOpenMenu={() => {}}
23
+ threadsPanel={<text>threads</text>}
24
+ threadTitle={LONG_TITLE}
25
+ threadPanel={<text>Select a thread</text>}
26
+ changesOpen
27
+ projectOpen
28
+ onToggleChanges={() => {}}
29
+ onToggleProject={() => {}}
30
+ onToggleRight={() => {}}
31
+ projectMode="changes"
32
+ projectPanel={<text>project</text>}
33
+ changesPanel={<text>diff</text>}
34
+ />,
35
+ { width: 160, height: 12 },
36
+ );
37
+
38
+ await settle(setup);
39
+ await settle(setup);
40
+ const lines = setup.captureCharFrame().split("\n");
41
+
42
+ setup.renderer.destroy();
43
+
44
+ return lines;
45
+ }
46
+
47
+ describe("collapsed-sidebar brand row", () => {
48
+ test("the Threads toggle stays on the cueloop row and the title tails off in an ellipsis", async () => {
49
+ const lines = await brandFrame();
50
+ const brandRow = lines.findIndex((line) => line.includes("cueloop"));
51
+ const toggleRow = lines.findIndex((line) => line.includes(NERD.sidebarLeftOff));
52
+
53
+ expect(brandRow).toBeGreaterThanOrEqual(0);
54
+ expect(toggleRow).toBe(brandRow);
55
+ expect(lines[brandRow]).toContain("…");
56
+ expect(lines[brandRow]).not.toContain(LONG_TITLE);
57
+ });
58
+ });
@@ -0,0 +1,191 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import React from "react";
6
+ import { testRender } from "@opentui/react/test-utils";
7
+ import {
8
+ ClientExtensionRegistry,
9
+ loadInstalledClientExtensions,
10
+ } from "../../integrations/client-extension-registry";
11
+ import { settle } from "../../testing/test-support";
12
+ import { AppShell } from "./AppShell";
13
+
14
+ function shell(
15
+ registry?: ClientExtensionRegistry,
16
+ onExtensionError?: (message: string) => void,
17
+ ): React.ReactNode {
18
+ return (
19
+ <AppShell
20
+ sidebarOpen
21
+ onToggleSidebar={() => {}}
22
+ onOpenMenu={() => {}}
23
+ threadsPanel={<text>threads list</text>}
24
+ threadTitle="Thread"
25
+ threadPanel={<text>thread body</text>}
26
+ changesOpen={false}
27
+ projectOpen
28
+ onToggleChanges={() => {}}
29
+ onToggleProject={() => {}}
30
+ onToggleRight={() => {}}
31
+ projectMode="tree"
32
+ focusedPane="project"
33
+ projectPanel={<text>project files</text>}
34
+ extensionRegistry={registry}
35
+ extensionContext={{ workspace: "/repo", threadId: "thread-1" }}
36
+ onExtensionError={onExtensionError}
37
+ />
38
+ );
39
+ }
40
+
41
+ describe("app shell extension zones", () => {
42
+ test("an empty registry preserves the built-in shell frame", async () => {
43
+ const plain = await testRender(shell(), { width: 100, height: 16 });
44
+ const extended = await testRender(shell(new ClientExtensionRegistry()), {
45
+ width: 100,
46
+ height: 16,
47
+ });
48
+
49
+ await settle(plain);
50
+ await settle(extended);
51
+ expect(extended.captureCharFrame()).toBe(plain.captureCharFrame());
52
+ plain.renderer.destroy();
53
+ extended.renderer.destroy();
54
+ });
55
+
56
+ test("renders a sidebar section and workspace tab without replacing built-in content", async () => {
57
+ const registry = new ClientExtensionRegistry();
58
+
59
+ await registry.load("example", (api) => {
60
+ api.registerSection({
61
+ id: "summary",
62
+ zone: "threads.sidebar",
63
+ title: "Summary",
64
+ Component: () => <text>extension summary</text>,
65
+ });
66
+ api.registerView({
67
+ id: "details",
68
+ zone: "workspace.panels",
69
+ title: "Details",
70
+ Component: () => <text>extension details</text>,
71
+ });
72
+ });
73
+ const setup = await testRender(shell(registry), { width: 100, height: 16 });
74
+
75
+ await settle(setup);
76
+ const frame = setup.captureCharFrame();
77
+
78
+ expect(frame).toContain("extension summary");
79
+ expect(frame).toContain("Details");
80
+ expect(frame).toContain("project files");
81
+ setup.mockInput.pressKey("]");
82
+ await settle(setup);
83
+ expect(setup.captureCharFrame()).toContain("extension details");
84
+ expect(setup.captureCharFrame()).not.toContain("project files");
85
+ setup.mockInput.pressKey("[");
86
+ await settle(setup);
87
+ expect(setup.captureCharFrame()).toContain("project files");
88
+ setup.renderer.destroy();
89
+ });
90
+
91
+ test("an installed client entry can use the host React hooks", async () => {
92
+ const root = mkdtempSync(join(tmpdir(), "cueloop-client-extension-test-"));
93
+ const packageRoot = join(root, "node_modules", "example-client");
94
+
95
+ try {
96
+ mkdirSync(packageRoot, { recursive: true });
97
+ writeFileSync(
98
+ join(root, "package.json"),
99
+ JSON.stringify({ dependencies: { "example-client": "1.0.0" } }),
100
+ );
101
+ writeFileSync(
102
+ join(packageRoot, "package.json"),
103
+ JSON.stringify({ cueloop: { client: "./client.js" } }),
104
+ );
105
+ writeFileSync(
106
+ join(packageRoot, "client.js"),
107
+ `export default (api) => {
108
+ const React = api.react;
109
+ api.registerSection({ id: "hook", zone: "threads.sidebar", title: "Host hook",
110
+ Component: () => { const [count] = React.useState(1); return React.createElement("text", null, String(count)); }
111
+ });
112
+ };`,
113
+ );
114
+ const registry = new ClientExtensionRegistry();
115
+
116
+ await loadInstalledClientExtensions(registry, root);
117
+ const setup = await testRender(shell(registry), { width: 100, height: 16 });
118
+
119
+ await settle(setup);
120
+ expect(registry.errors).toEqual([]);
121
+ expect(setup.captureCharFrame()).toContain("Host hook");
122
+ expect(setup.captureCharFrame()).toContain("1");
123
+ setup.renderer.destroy();
124
+ } finally {
125
+ rmSync(root, { recursive: true, force: true });
126
+ }
127
+ });
128
+
129
+ test("reports a failed client entry through the host error callback", async () => {
130
+ const registry = new ClientExtensionRegistry();
131
+ const errors: string[] = [];
132
+ const setup = await testRender(
133
+ shell(registry, (message) => errors.push(message)),
134
+ { width: 100, height: 16 },
135
+ );
136
+
137
+ registry.reportError("Client extension example: failed to load");
138
+ await settle(setup);
139
+ expect(errors).toEqual(["Client extension example: failed to load"]);
140
+ setup.renderer.destroy();
141
+ });
142
+
143
+ test("reports failed visibility callbacks for each UI zone", async () => {
144
+ const registry = new ClientExtensionRegistry();
145
+ const errors: string[] = [];
146
+ const when = () => {
147
+ throw new Error("visibility broke");
148
+ };
149
+
150
+ await registry.load("broken", (api) => {
151
+ api.registerSection({
152
+ id: "section",
153
+ zone: "threads.sidebar",
154
+ title: "Hidden section",
155
+ Component: () => <text>section body</text>,
156
+ when,
157
+ });
158
+ api.registerAction({
159
+ id: "action",
160
+ zone: "thread.header",
161
+ label: "Hidden action",
162
+ onPress() {},
163
+ when,
164
+ });
165
+ api.registerView({
166
+ id: "view",
167
+ zone: "workspace.panels",
168
+ title: "Hidden view",
169
+ Component: () => <text>view body</text>,
170
+ when,
171
+ });
172
+ });
173
+ const setup = await testRender(
174
+ shell(registry, (message) => errors.push(message)),
175
+ {
176
+ width: 100,
177
+ height: 16,
178
+ },
179
+ );
180
+
181
+ await settle(setup);
182
+ expect(errors).toEqual([
183
+ "Extension broken:section visibility failed: Error: visibility broke",
184
+ "Extension broken:action visibility failed: Error: visibility broke",
185
+ "Extension broken:view visibility failed: Error: visibility broke",
186
+ ]);
187
+ expect(setup.captureCharFrame()).not.toContain("Hidden section");
188
+ expect(setup.captureCharFrame()).not.toContain("Hidden view");
189
+ setup.renderer.destroy();
190
+ });
191
+ });
@@ -0,0 +1,89 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../stories/story";
4
+ import { AppShell } from "./AppShell";
5
+
6
+ export const meta: StoryMeta = { title: "Layout/AppShell" };
7
+
8
+ function Threads(): React.ReactNode {
9
+ return (
10
+ <box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
11
+ <text fg={DARK.textDim}>Pinned</text>
12
+ <text fg={DARK.accent}> Read Cueloop Repository</text>
13
+ <text> </text>
14
+ <text fg={DARK.textDim}>Threads</text>
15
+ <text fg={DARK.textMuted}> A standalone thought</text>
16
+ </box>
17
+ );
18
+ }
19
+
20
+ function Thread(): React.ReactNode {
21
+ return (
22
+ <box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
23
+ <text fg={DARK.text}>the conversation renders here</text>
24
+ </box>
25
+ );
26
+ }
27
+
28
+ function Changes(): React.ReactNode {
29
+ return (
30
+ <box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
31
+ <text fg={DARK.textMuted}>the editor grid renders here</text>
32
+ </box>
33
+ );
34
+ }
35
+
36
+ function Project(): React.ReactNode {
37
+ return (
38
+ <box style={{ flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
39
+ <text fg={DARK.textDim}>No changes</text>
40
+ </box>
41
+ );
42
+ }
43
+
44
+ const noop = (): void => {};
45
+
46
+ export const FourPane: Story = {
47
+ render: () => (
48
+ <AppShell
49
+ sidebarOpen
50
+ onToggleSidebar={noop}
51
+ onOpenMenu={noop}
52
+ threadsPanel={<Threads />}
53
+ threadTitle="Read Cueloop Repository"
54
+ threadPanel={<Thread />}
55
+ changesOpen
56
+ projectOpen
57
+ onToggleChanges={noop}
58
+ onToggleProject={noop}
59
+ onToggleRight={noop}
60
+ projectMode="changes"
61
+ changesPanel={<Changes />}
62
+ projectPanel={<Project />}
63
+ />
64
+ ),
65
+ expectedColors: [DARK.border, DARK.accent],
66
+ size: { width: 130, height: 28 },
67
+ };
68
+
69
+ export const CollapsedRightRail: Story = {
70
+ render: () => (
71
+ <AppShell
72
+ sidebarOpen
73
+ onToggleSidebar={noop}
74
+ onOpenMenu={noop}
75
+ threadsPanel={<Threads />}
76
+ threadTitle="Read Cueloop Repository"
77
+ threadPanel={<Thread />}
78
+ changesOpen={false}
79
+ projectOpen={false}
80
+ onToggleChanges={noop}
81
+ onToggleProject={noop}
82
+ onToggleRight={noop}
83
+ projectMode="tree"
84
+ projectPanel={<Project />}
85
+ />
86
+ ),
87
+ expectedColors: [DARK.border],
88
+ size: { width: 100, height: 22 },
89
+ };