@cueloop/client 0.1.0-alpha.11 → 0.1.0-alpha.111

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 (360) 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 +63 -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 +383 -0
  12. package/src/annotations/components/DiscussionMarkerRail.stories.tsx +54 -0
  13. package/src/annotations/components/DiscussionMarkerRail.tsx +189 -0
  14. package/src/annotations/discussions.ts +78 -0
  15. package/src/annotations/thread-selection.test.ts +210 -0
  16. package/src/annotations/thread-selection.ts +248 -0
  17. package/src/annotations/use-annotation-surface.tsx +1378 -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 +190 -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.harness-settings.test.tsx +77 -0
  25. package/src/app/App.identity.test.tsx +84 -0
  26. package/src/app/App.plan-render.test.tsx +211 -0
  27. package/src/app/App.plan-review.test.tsx +429 -0
  28. package/src/app/App.prototype-markdown.test.tsx +129 -0
  29. package/src/app/App.prototype.test.tsx +176 -0
  30. package/src/app/App.readonly.test.tsx +274 -0
  31. package/src/app/App.regression.test.tsx +99 -0
  32. package/src/app/App.rendering.test.tsx +134 -0
  33. package/src/app/App.share-dialog.test.tsx +177 -0
  34. package/src/app/App.submit-confirm.test.tsx +267 -0
  35. package/src/app/App.test.tsx +497 -0
  36. package/src/app/App.thread-agent.test.ts +20 -0
  37. package/src/app/App.toast.test.tsx +124 -0
  38. package/src/app/App.tsx +1758 -0
  39. package/src/app/App.visual.test.tsx +60 -0
  40. package/src/app/App.walk.test.tsx +300 -0
  41. package/src/app/App.workbench.test.tsx +1153 -0
  42. package/src/app/app-key-state.ts +61 -0
  43. package/src/app/app-screens.tsx +607 -0
  44. package/src/app/app-view-model.ts +178 -0
  45. package/src/app/components/AppHeader.stories.tsx +50 -0
  46. package/src/app/components/AppHeader.tsx +81 -0
  47. package/src/app/components/AppShell.brand.test.tsx +58 -0
  48. package/src/app/components/AppShell.extensions.test.tsx +191 -0
  49. package/src/app/components/AppShell.stories.tsx +89 -0
  50. package/src/app/components/AppShell.tsx +358 -0
  51. package/src/app/components/AppShell.zoom.test.tsx +149 -0
  52. package/src/app/components/Breadcrumb.stories.tsx +19 -0
  53. package/src/app/components/Breadcrumb.tsx +45 -0
  54. package/src/app/components/MenuBar.stories.tsx +28 -0
  55. package/src/app/components/MenuBar.tsx +44 -0
  56. package/src/app/components/PaneGrid.stories.tsx +84 -0
  57. package/src/app/components/PaneGrid.tsx +128 -0
  58. package/src/app/components/PanelColumn.stories.tsx +25 -0
  59. package/src/app/components/PanelColumn.tsx +93 -0
  60. package/src/app/components/ShellHeader.stories.tsx +50 -0
  61. package/src/app/components/ShellHeader.tsx +137 -0
  62. package/src/app/components/WorkspacePanels.stories.tsx +36 -0
  63. package/src/app/components/WorkspacePanels.tsx +121 -0
  64. package/src/app/components/pages/Workspace.stories.tsx +181 -0
  65. package/src/app/intent-dispatch.test.ts +729 -0
  66. package/src/app/intent-dispatch.ts +713 -0
  67. package/src/app/launch-layout.test.ts +78 -0
  68. package/src/app/launch-layout.ts +57 -0
  69. package/src/app/ready-signal.test.ts +85 -0
  70. package/src/app/ready-signal.ts +54 -0
  71. package/src/app/run.ts +111 -0
  72. package/src/{serve.test.ts → app/serve.test.ts} +28 -12
  73. package/src/app/serve.ts +111 -0
  74. package/src/app/version.ts +5 -0
  75. package/src/appearance/components/theme-context.stories.tsx +46 -0
  76. package/src/appearance/components/theme-context.tsx +37 -0
  77. package/src/appearance/theme-presets.test.ts +51 -0
  78. package/src/appearance/theme-presets.ts +219 -0
  79. package/src/appearance/theme.ts +112 -0
  80. package/src/appearance/visual/contrast.test.ts +35 -0
  81. package/src/appearance/visual/contrast.ts +33 -0
  82. package/src/diff/components/AnnotatableFileView.stories.tsx +42 -0
  83. package/src/diff/components/AnnotatableFileView.test.tsx +160 -0
  84. package/src/diff/components/AnnotatableFileView.tsx +142 -0
  85. package/src/diff/components/BareWorkbenchFileView.stories.tsx +27 -0
  86. package/src/diff/components/BareWorkbenchFileView.tsx +63 -0
  87. package/src/diff/components/DiffContentView.scroll.test.tsx +443 -0
  88. package/src/diff/components/DiffContentView.split.test.tsx +140 -0
  89. package/src/diff/components/DiffContentView.stories.tsx +80 -0
  90. package/src/diff/components/DiffContentView.syntax.test.tsx +85 -0
  91. package/src/diff/components/DiffContentView.tsx +1080 -0
  92. package/src/diff/components/WalkWizard.stories.tsx +100 -0
  93. package/src/diff/components/WalkWizard.tsx +146 -0
  94. package/src/diff/components/diff-content-view-layout.test.ts +198 -0
  95. package/src/diff/components/diff-content-view-layout.ts +87 -0
  96. package/src/diff/diff-fold.test.ts +78 -0
  97. package/src/diff/diff-fold.ts +130 -0
  98. package/src/diff/diff-hunk-curate.test.ts +315 -0
  99. package/src/diff/diff-hunk-curate.ts +155 -0
  100. package/src/diff/diff-intraline.test.ts +239 -0
  101. package/src/diff/diff-intraline.ts +244 -0
  102. package/src/diff/diff-syntax.test.ts +125 -0
  103. package/src/diff/diff-syntax.ts +194 -0
  104. package/src/diff/split-diff.test.ts +110 -0
  105. package/src/diff/split-diff.ts +150 -0
  106. package/src/diff/view-diff.test.ts +325 -0
  107. package/src/diff/view-diff.ts +540 -0
  108. package/src/diff/walk.test.ts +121 -0
  109. package/src/diff/walk.ts +76 -0
  110. package/src/index.ts +32 -2
  111. package/src/integrations/attribution.test.ts +31 -0
  112. package/src/integrations/attribution.ts +44 -0
  113. package/src/integrations/client-extension-registry.test.tsx +61 -0
  114. package/src/integrations/client-extension-registry.ts +163 -0
  115. package/src/integrations/clipboard.test.ts +23 -0
  116. package/src/integrations/clipboard.ts +34 -0
  117. package/src/integrations/components/ExtensionZone.stories.tsx +28 -0
  118. package/src/integrations/components/ExtensionZone.tsx +145 -0
  119. package/src/integrations/components/ShareDialog.agent.test.tsx +52 -0
  120. package/src/integrations/components/ShareDialog.stories.tsx +121 -0
  121. package/src/integrations/components/ShareDialog.tsx +602 -0
  122. package/src/integrations/components/share-dialog-store.ts +59 -0
  123. package/src/integrations/editor.test.ts +149 -0
  124. package/src/integrations/editor.ts +163 -0
  125. package/src/integrations/github-identity.test.ts +59 -0
  126. package/src/integrations/github-identity.ts +42 -0
  127. package/src/integrations/herdr.test.ts +43 -0
  128. package/src/integrations/herdr.ts +40 -0
  129. package/src/integrations/integrations.test.ts +41 -0
  130. package/src/integrations/integrations.ts +32 -0
  131. package/src/integrations/pasted-image.test.ts +67 -0
  132. package/src/integrations/pasted-image.ts +62 -0
  133. package/src/integrations/share.ts +283 -0
  134. package/src/keyboard/components/CompletionOverlay.stories.tsx +37 -0
  135. package/src/keyboard/components/CompletionOverlay.tsx +75 -0
  136. package/src/keyboard/components/HandleChipsInput.stories.tsx +24 -0
  137. package/src/keyboard/components/HandleChipsInput.test.tsx +35 -0
  138. package/src/keyboard/components/HandleChipsInput.tsx +147 -0
  139. package/src/keyboard/components/KeybindsDialog.stories.tsx +31 -0
  140. package/src/keyboard/components/KeybindsDialog.tsx +103 -0
  141. package/src/keyboard/components/NavModeHint.stories.tsx +40 -0
  142. package/src/keyboard/components/NavModeHint.tsx +31 -0
  143. package/src/keyboard/components/SlashComposer.stories.tsx +37 -0
  144. package/src/keyboard/components/SlashComposer.tsx +90 -0
  145. package/src/keyboard/key-bindings.test.ts +156 -0
  146. package/src/keyboard/key-bindings.ts +462 -0
  147. package/src/keyboard/keymap.test.ts +611 -0
  148. package/src/keyboard/keymap.ts +446 -0
  149. package/src/keyboard/skills.test.ts +74 -0
  150. package/src/keyboard/skills.ts +77 -0
  151. package/src/keyboard/slash-palette.test.ts +103 -0
  152. package/src/keyboard/slash-palette.ts +137 -0
  153. package/src/keyboard/thread-chords.test.ts +79 -0
  154. package/src/keyboard/thread-chords.ts +168 -0
  155. package/src/keyboard/use-shared-keyboard.ts +56 -0
  156. package/src/markdown/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  157. package/src/markdown/components/MarkdownGridBlock.stories.tsx +33 -0
  158. package/src/markdown/components/MarkdownGridBlock.tsx +72 -0
  159. package/src/markdown/components/MarkdownThreadEditor.stories.tsx +15 -0
  160. package/src/markdown/components/MarkdownThreadEditor.test.tsx +98 -0
  161. package/src/markdown/components/MarkdownThreadEditor.tsx +165 -0
  162. package/src/markdown/components/ThreadView.selection.test.tsx +954 -0
  163. package/src/markdown/components/ThreadView.slash.test.tsx +146 -0
  164. package/src/markdown/components/ThreadView.stories.tsx +26 -0
  165. package/src/markdown/components/ThreadView.tsx +577 -0
  166. package/src/markdown/mark-runs.ts +125 -0
  167. package/src/markdown/markdown-editor-syntax-style.ts +66 -0
  168. package/src/markdown/markdown-grid.test.ts +101 -0
  169. package/src/markdown/markdown-grid.ts +209 -0
  170. package/src/markdown/markdown-highlight.test.ts +61 -0
  171. package/src/markdown/markdown-highlight.ts +125 -0
  172. package/src/markdown/thread-code-syntax.ts +48 -0
  173. package/src/markdown/use-thread-body-editing.tsx +54 -0
  174. package/src/markdown/view-plan.regression.test.ts +60 -0
  175. package/src/markdown/view-plan.test.ts +545 -0
  176. package/src/markdown/view-plan.ts +1061 -0
  177. package/src/performance/otlp-trace.test.ts +119 -0
  178. package/src/performance/otlp-trace.ts +134 -0
  179. package/src/performance/perf-report.ts +19 -0
  180. package/src/performance/perf-timings.test.ts +18 -0
  181. package/src/performance/perf-timings.ts +40 -0
  182. package/src/prototype/components/PrototypeContentView.stories.tsx +19 -0
  183. package/src/prototype/components/PrototypeContentView.tsx +459 -0
  184. package/src/prototype/prototype-browser.test.ts +81 -0
  185. package/src/prototype/prototype-browser.ts +332 -0
  186. package/src/prototype/prototype-pixels.tsx +35 -0
  187. package/src/settings/components/SettingsDialog.stories.tsx +117 -0
  188. package/src/settings/components/SettingsDialog.tsx +186 -0
  189. package/src/settings/components/SettingsRows.stories.tsx +18 -0
  190. package/src/settings/components/SettingsRows.tsx +112 -0
  191. package/src/settings/components/quick-actions-editor.stories.tsx +58 -0
  192. package/src/settings/components/quick-actions-editor.test.tsx +120 -0
  193. package/src/settings/components/quick-actions-editor.tsx +222 -0
  194. package/src/settings/config-identity.test.ts +78 -0
  195. package/src/settings/config.test.ts +771 -0
  196. package/src/settings/config.ts +722 -0
  197. package/src/settings/use-settings-dialog.test.tsx +172 -0
  198. package/src/settings/use-settings-dialog.tsx +465 -0
  199. package/src/settings/use-thread-harness-setting.test.tsx +87 -0
  200. package/src/settings/use-thread-harness-setting.ts +86 -0
  201. package/src/stories/__snapshots__/stories.test.tsx.snap +2358 -0
  202. package/src/stories/stories-app.tsx +196 -0
  203. package/src/stories/stories.test.tsx +78 -0
  204. package/src/stories/story-fixtures.ts +102 -0
  205. package/src/stories/story-tree.test.ts +108 -0
  206. package/src/stories/story.ts +160 -0
  207. package/src/terminal/ansi-screen.test.ts +28 -0
  208. package/src/terminal/ansi-screen.ts +77 -0
  209. package/src/terminal/components/AnsiScreen.stories.tsx +12 -0
  210. package/src/terminal/components/AnsiScreen.tsx +28 -0
  211. package/src/terminal/components/terminal-pane.test.tsx +40 -0
  212. package/src/terminal/components/terminal-pane.ts +208 -0
  213. package/src/terminal/ghostty-terminal.test.ts +85 -0
  214. package/src/terminal/ghostty-terminal.ts +207 -0
  215. package/src/terminal/kitty-image.test.ts +66 -0
  216. package/src/terminal/kitty-image.ts +140 -0
  217. package/src/terminal/pty.test.ts +81 -0
  218. package/src/terminal/pty.ts +255 -0
  219. package/src/testing/test-support.test.ts +266 -0
  220. package/src/testing/test-support.ts +449 -0
  221. package/src/thread/agent-continuation.test.ts +34 -0
  222. package/src/thread/agent-continuation.ts +13 -0
  223. package/src/thread/agent-transcript.test.ts +161 -0
  224. package/src/thread/agent-transcript.ts +344 -0
  225. package/src/thread/components/AgentConfigControls.stories.tsx +39 -0
  226. package/src/thread/components/AgentConfigControls.test.tsx +153 -0
  227. package/src/thread/components/AgentConfigControls.tsx +121 -0
  228. package/src/thread/components/AgentThreadPane.stories.tsx +105 -0
  229. package/src/thread/components/AgentThreadPane.test.tsx +1248 -0
  230. package/src/thread/components/AgentThreadPane.tsx +458 -0
  231. package/src/thread/components/ThreadFooter.stories.tsx +30 -0
  232. package/src/thread/components/ThreadFooter.test.tsx +49 -0
  233. package/src/thread/components/ThreadFooter.tsx +77 -0
  234. package/src/thread/components/ThreadTree.stories.tsx +46 -0
  235. package/src/thread/components/ThreadTree.test.tsx +41 -0
  236. package/src/thread/components/ThreadTree.tsx +268 -0
  237. package/src/thread/components/thread-tree.test.ts +138 -0
  238. package/src/thread/components/thread-tree.ts +112 -0
  239. package/src/thread/thread-controller.curate.test.ts +504 -0
  240. package/src/thread/thread-controller.derivation.test.ts +62 -0
  241. package/src/thread/thread-controller.live-diff.test.ts +357 -0
  242. package/src/thread/thread-controller.optimistic.test.ts +69 -0
  243. package/src/thread/thread-controller.share.test.ts +618 -0
  244. package/src/thread/thread-controller.switch-cache.test.ts +174 -0
  245. package/src/thread/thread-controller.tree.test.ts +263 -0
  246. package/src/thread/thread-controller.ts +2151 -0
  247. package/src/thread/tree-view.test.ts +128 -0
  248. package/src/thread/tree-view.ts +150 -0
  249. package/src/thread/use-thread-agent.test.tsx +209 -0
  250. package/src/thread/use-thread-agent.ts +122 -0
  251. package/src/ui/components/CodeBlock.stories.tsx +42 -0
  252. package/src/ui/components/CodeBlock.tsx +82 -0
  253. package/src/ui/components/ConfirmCard.stories.tsx +37 -0
  254. package/src/ui/components/ConfirmCard.tsx +151 -0
  255. package/src/ui/components/ConfirmDialog.stories.tsx +21 -0
  256. package/src/ui/components/ConfirmDialog.tsx +72 -0
  257. package/src/ui/components/OverlayScrollbar.stories.tsx +33 -0
  258. package/src/ui/components/OverlayScrollbar.test.tsx +142 -0
  259. package/src/ui/components/OverlayScrollbar.tsx +134 -0
  260. package/src/ui/components/PromptDialog.stories.tsx +36 -0
  261. package/src/ui/components/PromptDialog.test.tsx +65 -0
  262. package/src/ui/components/PromptDialog.tsx +101 -0
  263. package/src/ui/components/RootOverlay.stories.tsx +45 -0
  264. package/src/ui/components/RootOverlay.tsx +50 -0
  265. package/src/ui/components/ScrollArea.stories.tsx +20 -0
  266. package/src/ui/components/ScrollArea.tsx +63 -0
  267. package/src/ui/components/Toast.stories.tsx +19 -0
  268. package/src/ui/components/Toast.tsx +54 -0
  269. package/src/ui/components/Tooltip.stories.tsx +26 -0
  270. package/src/ui/components/Tooltip.tsx +93 -0
  271. package/src/ui/components/menu-control.stories.tsx +35 -0
  272. package/src/ui/components/menu-control.tsx +43 -0
  273. package/src/ui/components/primitives/Button.stories.tsx +32 -0
  274. package/src/ui/components/primitives/Button.tsx +58 -0
  275. package/src/ui/components/primitives/Card.stories.tsx +25 -0
  276. package/src/ui/components/primitives/Card.tsx +62 -0
  277. package/src/ui/components/primitives/Dialog.stories.tsx +24 -0
  278. package/src/ui/components/primitives/Dialog.test.tsx +38 -0
  279. package/src/ui/components/primitives/Dialog.tsx +69 -0
  280. package/src/ui/components/primitives/DialogActions.stories.tsx +20 -0
  281. package/src/ui/components/primitives/DialogActions.tsx +39 -0
  282. package/src/ui/components/primitives/IconButton.stories.tsx +36 -0
  283. package/src/ui/components/primitives/IconButton.tsx +58 -0
  284. package/src/ui/components/primitives/StatusBar.stories.tsx +11 -0
  285. package/src/ui/components/primitives/StatusBar.tsx +23 -0
  286. package/src/ui/components/primitives/Switch.stories.tsx +17 -0
  287. package/src/ui/components/primitives/Switch.tsx +32 -0
  288. package/src/ui/components/primitives/Tabs.stories.tsx +29 -0
  289. package/src/ui/components/primitives/Tabs.tsx +102 -0
  290. package/src/ui/components/primitives/Toolbar.stories.tsx +17 -0
  291. package/src/ui/components/primitives/Toolbar.tsx +14 -0
  292. package/src/ui/components/primitives/Tree.stories.tsx +124 -0
  293. package/src/ui/components/primitives/Tree.tsx +137 -0
  294. package/src/ui/components/primitives/frame.test.ts +12 -0
  295. package/src/ui/components/primitives/frame.ts +17 -0
  296. package/src/ui/components/primitives/icons.ts +61 -0
  297. package/src/ui/components/primitives/tree-model.test.ts +99 -0
  298. package/src/ui/components/primitives/tree-model.ts +144 -0
  299. package/src/ui/components/syntax-highlight.ts +154 -0
  300. package/src/ui/components/truncate-text.ts +7 -0
  301. package/src/ui/components/truncate-title.test.ts +31 -0
  302. package/src/ui/components/truncate-title.ts +16 -0
  303. package/src/ui/use-frame-measure.ts +88 -0
  304. package/src/ui/use-terminal-virtualizer.ts +286 -0
  305. package/src/workbench/components/ChangesColumn.stories.tsx +44 -0
  306. package/src/workbench/components/ChangesColumn.tsx +210 -0
  307. package/src/workbench/components/ChangesFileTree.test.tsx +184 -0
  308. package/src/workbench/components/EditorGrid.split.test.tsx +119 -0
  309. package/src/workbench/components/EditorGrid.stories.tsx +86 -0
  310. package/src/workbench/components/EditorGrid.tabs.test.tsx +118 -0
  311. package/src/workbench/components/EditorGrid.tsx +552 -0
  312. package/src/workbench/components/EditorGrid.zoom.test.tsx +132 -0
  313. package/src/workbench/components/FileContentsView.stories.tsx +16 -0
  314. package/src/workbench/components/FileContentsView.test.tsx +29 -0
  315. package/src/workbench/components/FileContentsView.tsx +104 -0
  316. package/src/workbench/components/GridTabContent.actions.test.tsx +151 -0
  317. package/src/workbench/components/GridTabContent.stories.tsx +59 -0
  318. package/src/workbench/components/GridTabContent.tsx +195 -0
  319. package/src/workbench/components/ProjectTreeView.stories.tsx +25 -0
  320. package/src/workbench/components/ProjectTreeView.test.tsx +239 -0
  321. package/src/workbench/components/ProjectTreeView.tsx +159 -0
  322. package/src/workbench/components/TreePane.stories.tsx +106 -0
  323. package/src/workbench/components/TreePane.tsx +157 -0
  324. package/src/workbench/components/WelcomePlayground.stories.tsx +12 -0
  325. package/src/workbench/components/WelcomePlayground.test.tsx +83 -0
  326. package/src/workbench/components/WelcomePlayground.tsx +138 -0
  327. package/src/workbench/components/editor-grid.test.ts +184 -0
  328. package/src/workbench/components/editor-grid.ts +284 -0
  329. package/src/workbench/components/file-tree.test.ts +60 -0
  330. package/src/workbench/components/file-tree.ts +114 -0
  331. package/src/workbench/use-changes-workbench.ts +210 -0
  332. package/src/workbench/use-remember-layout.ts +33 -0
  333. package/src/workbench/use-repo-changes.ts +93 -0
  334. package/src/workbench/workbench-snapshot.test.ts +99 -0
  335. package/src/workbench/workbench-snapshot.ts +59 -0
  336. package/src/App.diff.test.tsx +0 -99
  337. package/src/App.export.test.tsx +0 -88
  338. package/src/App.plan-review.test.tsx +0 -259
  339. package/src/App.readonly.test.tsx +0 -148
  340. package/src/App.rendering.test.tsx +0 -108
  341. package/src/App.submit-confirm.test.tsx +0 -183
  342. package/src/App.test.tsx +0 -218
  343. package/src/App.tsx +0 -1249
  344. package/src/config.test.ts +0 -116
  345. package/src/config.ts +0 -150
  346. package/src/editor.ts +0 -34
  347. package/src/herdr.test.ts +0 -68
  348. package/src/herdr.ts +0 -34
  349. package/src/keymap.test.ts +0 -270
  350. package/src/keymap.ts +0 -173
  351. package/src/run.ts +0 -30
  352. package/src/serve.ts +0 -75
  353. package/src/session-controller.ts +0 -398
  354. package/src/syntax.test.ts +0 -41
  355. package/src/syntax.ts +0 -88
  356. package/src/theme.ts +0 -46
  357. package/src/view-diff.test.ts +0 -55
  358. package/src/view-diff.ts +0 -86
  359. package/src/view.test.ts +0 -157
  360. package/src/view.ts +0 -364
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Horizontal action row: children flow left to right on one row. The classic
3
+ * home of Button groups (Save/Cancel, Submit/Cancel).
4
+ */
5
+
6
+ import React from "react";
7
+
8
+ export interface ToolbarProps {
9
+ children: React.ReactNode;
10
+ }
11
+
12
+ export function Toolbar({ children }: ToolbarProps): React.ReactNode {
13
+ return <box style={{ flexDirection: "row", height: 1 }}>{children}</box>;
14
+ }
@@ -0,0 +1,124 @@
1
+ import React from "react";
2
+ import { DARK } from "../../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../../stories/story";
4
+ import { Tree } from "./Tree";
5
+ import type { TreeNode } from "./tree-model";
6
+
7
+ export const meta: StoryMeta = { title: "Primitives/Tree" };
8
+
9
+ const FILES: TreeNode[] = [
10
+ {
11
+ id: "packages",
12
+ label: "packages",
13
+ children: [
14
+ {
15
+ id: "schema",
16
+ label: "schema",
17
+ children: [
18
+ { id: "schema/types.ts", label: "types.ts", status: "modified" },
19
+ { id: "schema/tree-model.ts", label: "tree-model.ts", status: "added" },
20
+ ],
21
+ },
22
+ {
23
+ id: "daemon",
24
+ label: "daemon",
25
+ children: [{ id: "daemon/api.ts", label: "api.ts", status: "modified" }],
26
+ },
27
+ ],
28
+ },
29
+ { id: "README.md", label: "README.md" },
30
+ { id: "old.ts", label: "old.ts", status: "deleted" },
31
+ ];
32
+
33
+ const DEEP: TreeNode[] = [
34
+ {
35
+ id: "src",
36
+ label: "src",
37
+ children: [
38
+ {
39
+ id: "src/components",
40
+ label: "components",
41
+ children: [
42
+ {
43
+ id: "src/components/primitives",
44
+ label: "primitives",
45
+ children: [
46
+ { id: "src/components/primitives/Tree.tsx", label: "Tree.tsx", status: "added" },
47
+ ],
48
+ },
49
+ ],
50
+ },
51
+ ],
52
+ },
53
+ ];
54
+
55
+ const PROJECTS: TreeNode[] = [
56
+ {
57
+ id: "projects",
58
+ label: "Projects",
59
+ children: [
60
+ {
61
+ id: "cueloop",
62
+ label: "cueloop",
63
+ children: [{ id: "cueloop/read", label: "Read Cueloop Repository" }],
64
+ },
65
+ ],
66
+ },
67
+ {
68
+ id: "threads",
69
+ label: "Threads",
70
+ children: [{ id: "threads/welcome", label: "Welcome to cueloop" }],
71
+ },
72
+ ];
73
+
74
+ function Frame({ children }: { children: React.ReactNode }): React.ReactNode {
75
+ return (
76
+ <box style={{ width: 40, height: "100%", backgroundColor: DARK.panel, paddingTop: 1 }}>
77
+ {children}
78
+ </box>
79
+ );
80
+ }
81
+
82
+ export const FileTree: Story = {
83
+ render: () => (
84
+ <Frame>
85
+ <Tree
86
+ nodes={FILES}
87
+ expandedIds={new Set(["packages", "schema", "daemon"])}
88
+ selectedId="schema/tree-model.ts"
89
+ showStatus
90
+ />
91
+ </Frame>
92
+ ),
93
+ expectedColors: [DARK.accent, DARK.green, DARK.red],
94
+ size: { width: 44, height: 16 },
95
+ };
96
+
97
+ export const FlattenedChain: Story = {
98
+ render: () => (
99
+ <Frame>
100
+ <Tree
101
+ nodes={DEEP}
102
+ expandedIds={new Set(["src", "src/components/primitives"])}
103
+ flattenEmptyDirectories
104
+ showStatus
105
+ />
106
+ </Frame>
107
+ ),
108
+ expectedColors: [DARK.green],
109
+ size: { width: 44, height: 10 },
110
+ };
111
+
112
+ export const ProjectsThreads: Story = {
113
+ render: () => (
114
+ <Frame>
115
+ <Tree
116
+ nodes={PROJECTS}
117
+ expandedIds={new Set(["projects", "cueloop", "threads"])}
118
+ selectedId="cueloop/read"
119
+ />
120
+ </Frame>
121
+ ),
122
+ expectedColors: [DARK.accent],
123
+ size: { width: 44, height: 12 },
124
+ };
@@ -0,0 +1,137 @@
1
+ // OpenTUI renderer for the headless tree-model; the caller owns expansion and selection.
2
+
3
+ import React, { useRef } from "react";
4
+ import { DARK, type Theme } from "../../../appearance/theme";
5
+ import { isDoubleClick, type ClickStamp } from "../../../annotations/thread-selection";
6
+ import { flattenTree, statusMeta, type TreeNode, type TreeTone } from "./tree-model";
7
+ import { NERD_TREE_ICONS, type TreeIcons } from "./icons";
8
+
9
+ export interface TreeProps {
10
+ nodes: readonly TreeNode[];
11
+ expandedIds: ReadonlySet<string>;
12
+ selectedId?: string;
13
+ flattenEmptyDirectories?: boolean;
14
+ showStatus?: boolean;
15
+ hideIcons?: boolean;
16
+ singleLine?: boolean;
17
+ selectedBackground?: string;
18
+ indentWidth?: number;
19
+ icons?: TreeIcons;
20
+ onSelect?: (id: string) => void;
21
+ onDoubleSelect?: (id: string) => void;
22
+ onToggle?: (id: string) => void;
23
+ theme?: Theme;
24
+ }
25
+
26
+ function rowGlyph(isFolder: boolean, expanded: boolean, icons: TreeIcons): string {
27
+ if (!isFolder) return icons.leaf;
28
+
29
+ return expanded ? icons.expanded : icons.collapsed;
30
+ }
31
+
32
+ function toneColor(tone: TreeTone, theme: Theme): string {
33
+ if (tone === "green") return theme.green;
34
+
35
+ if (tone === "blue") return theme.blue;
36
+
37
+ if (tone === "red") return theme.red;
38
+
39
+ return theme.textDim;
40
+ }
41
+
42
+ export function Tree({
43
+ nodes,
44
+ expandedIds,
45
+ selectedId,
46
+ flattenEmptyDirectories,
47
+ showStatus,
48
+ hideIcons,
49
+ singleLine = false,
50
+ selectedBackground,
51
+ indentWidth = 2,
52
+ icons = NERD_TREE_ICONS,
53
+ onSelect,
54
+ onDoubleSelect,
55
+ onToggle,
56
+ theme,
57
+ }: TreeProps): React.ReactNode {
58
+ const tokens = theme ?? DARK;
59
+ const selectedBackdrop = selectedBackground ?? tokens.elevated;
60
+ const rows = flattenTree(nodes, { expandedIds, flattenEmptyDirectories });
61
+ const lastClick = useRef<{ id: string; stamp: ClickStamp } | null>(null);
62
+
63
+ return (
64
+ <box style={{ flexDirection: "column" }}>
65
+ {rows.map((row) => {
66
+ const selected = row.id === selectedId;
67
+ const status = showStatus && row.status !== undefined ? statusMeta(row.status) : null;
68
+ const labelColor = selected
69
+ ? tokens.text
70
+ : status
71
+ ? toneColor(status.tone, tokens)
72
+ : row.isFolder
73
+ ? tokens.text
74
+ : tokens.textMuted;
75
+
76
+ return (
77
+ <box
78
+ key={row.id}
79
+ id={`tree-row-${row.id}`}
80
+ style={{
81
+ flexDirection: "row",
82
+ justifyContent: "space-between",
83
+ height: singleLine ? 1 : undefined,
84
+ overflow: singleLine ? "hidden" : undefined,
85
+ paddingLeft: 1 + row.depth * indentWidth,
86
+ paddingRight: singleLine && status ? 0 : 1,
87
+ backgroundColor: selected ? selectedBackdrop : undefined,
88
+ }}
89
+ onMouseUp={(event) => {
90
+ const stamp = { time: Date.now(), x: event.x, y: event.y };
91
+ const doubleClick =
92
+ lastClick.current?.id === row.id && isDoubleClick(lastClick.current.stamp, stamp);
93
+
94
+ lastClick.current = { id: row.id, stamp };
95
+
96
+ if (row.isFolder) onToggle?.(row.id);
97
+ else {
98
+ onSelect?.(row.id);
99
+
100
+ if (doubleClick) onDoubleSelect?.(row.id);
101
+ }
102
+ }}
103
+ >
104
+ <box
105
+ style={{
106
+ flexDirection: "row",
107
+ flexShrink: 1,
108
+ minWidth: 0,
109
+ overflow: singleLine ? "hidden" : undefined,
110
+ }}
111
+ >
112
+ {hideIcons ? null : (
113
+ <text fg={row.isFolder ? tokens.blue : tokens.textDim}>
114
+ {row.icon ?? rowGlyph(row.isFolder, row.expanded, icons)}{" "}
115
+ </text>
116
+ )}
117
+ <box style={{ flexShrink: 1, minWidth: 0 }}>
118
+ <text
119
+ id={`tree-label-${row.id}`}
120
+ fg={selected ? tokens.accent : labelColor}
121
+ wrapMode={singleLine ? "none" : undefined}
122
+ truncate={!singleLine}
123
+ >
124
+ {singleLine ? `${row.label} ` : row.label}
125
+ </text>
126
+ </box>
127
+ </box>
128
+ <box style={{ flexDirection: "row", flexShrink: 0 }}>
129
+ {row.badge !== undefined ? <text fg={tokens.textDim}>{row.badge}</text> : null}
130
+ {status ? <text fg={toneColor(status.tone, tokens)}> {status.letter}</text> : null}
131
+ </box>
132
+ </box>
133
+ );
134
+ })}
135
+ </box>
136
+ );
137
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Locks the single frame border rule: every bordered frame reads this token, so
3
+ * a drift back to rounded (or any other style) fails here first, before the
4
+ * slower VRT snapshots catch it.
5
+ */
6
+
7
+ import { expect, test } from "bun:test";
8
+ import { FRAME_BORDER_STYLE } from "./frame";
9
+
10
+ test("frames use the square border style", () => {
11
+ expect(FRAME_BORDER_STYLE).toBe("single");
12
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The one frame border rule for the design system. Every bordered frame -
3
+ * cards, dialogs, and the stories gallery chrome - reads its corner style
4
+ * from FRAME_BORDER_STYLE, so the corner decision lives in a single place and
5
+ * can never drift between frames. Buttons stay text-first and borderless: the
6
+ * frame they sit in carries the border, not the button.
7
+ */
8
+
9
+ import type { BorderStyle } from "@opentui/core";
10
+
11
+ /** Corner style shared by every bordered frame in the app: square, not rounded. */
12
+ export const FRAME_BORDER_STYLE: BorderStyle = "single";
13
+
14
+ /** Every frame's border title renders lowercase, so the convention holds without each caller minding case. */
15
+ export function frameTitle(title: string | undefined): string | undefined {
16
+ return title?.toLowerCase();
17
+ }
@@ -0,0 +1,61 @@
1
+ // Nerd Font glyphs (Private Use Area / codicons) plus the plus-minus diff mark; render as icons under a Nerd Font, tofu without one.
2
+
3
+ export const NERD = {
4
+ folderClosed: "",
5
+ folderOpen: "",
6
+ file: "",
7
+ chevronRight: "",
8
+ chevronDown: "",
9
+ settings: "",
10
+ search: "",
11
+ expand: "",
12
+ diff: "",
13
+ copy: "",
14
+ unfold: "",
15
+ kebab: "⋮",
16
+ star: "★",
17
+ close: "✕",
18
+ listTree: "",
19
+ sidebarLeft: "",
20
+ sidebarLeftOff: "",
21
+ sidebarRight: "",
22
+ sidebarRightOff: "",
23
+ submit: "",
24
+ zoom: "⛶",
25
+ split: "",
26
+ } as const;
27
+
28
+ // The thin rule under each pane header: an overline sits at the top edge of its cell so it hugs the
29
+ // header text (unlike the default light-horizontal, which floats mid-cell); painted in divider gray upstream.
30
+ export const HEADER_UNDERLINE_CHARS = {
31
+ topLeft: "\u250c",
32
+ topRight: "\u2510",
33
+ bottomLeft: "\u2514",
34
+ bottomRight: "\u2518",
35
+ horizontal: "\u203e",
36
+ vertical: "\u2502",
37
+ topT: "\u252c",
38
+ bottomT: "\u2534",
39
+ leftT: "\u251c",
40
+ rightT: "\u2524",
41
+ cross: "\u253c",
42
+ } as const;
43
+
44
+ export interface TreeIcons {
45
+ collapsed: string;
46
+ expanded: string;
47
+ leaf: string;
48
+ }
49
+
50
+ export const NERD_TREE_ICONS: TreeIcons = {
51
+ collapsed: NERD.folderClosed,
52
+ expanded: NERD.folderOpen,
53
+ leaf: NERD.file,
54
+ };
55
+
56
+ // Fallback for terminals without a Nerd Font.
57
+ export const ASCII_TREE_ICONS: TreeIcons = {
58
+ collapsed: "▸",
59
+ expanded: "▾",
60
+ leaf: " ",
61
+ };
@@ -0,0 +1,99 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { allFolderIds, flattenTree, statusMeta, type TreeNode } from "./tree-model";
3
+
4
+ const TREE: TreeNode[] = [
5
+ {
6
+ id: "src",
7
+ label: "src",
8
+ children: [
9
+ { id: "src/a.ts", label: "a.ts", status: "modified" },
10
+ {
11
+ id: "src/nested",
12
+ label: "nested",
13
+ children: [
14
+ {
15
+ id: "src/nested/only",
16
+ label: "only",
17
+ children: [{ id: "deep", label: "deep.ts", status: "added" }],
18
+ },
19
+ ],
20
+ },
21
+ ],
22
+ },
23
+ { id: "readme", label: "README.md" },
24
+ ];
25
+
26
+ describe("flattenTree", () => {
27
+ test("a collapsed folder shows only itself; expanding it reveals its children", () => {
28
+ // Act
29
+ const collapsed = flattenTree(TREE, { expandedIds: new Set() });
30
+ const rows = flattenTree(TREE, { expandedIds: new Set(["src"]) });
31
+
32
+ // Assert
33
+ expect(collapsed.map((row) => row.label)).toEqual(["src", "README.md"]);
34
+ expect(rows.map((row) => [row.label, row.depth, row.isFolder])).toEqual([
35
+ ["src", 0, true],
36
+ ["a.ts", 1, false],
37
+ ["nested", 1, true],
38
+ ["README.md", 0, false],
39
+ ]);
40
+ expect(rows.find((row) => row.label === "src")!.expanded).toBe(true);
41
+ expect(rows.find((row) => row.label === "a.ts")!.status).toBe("modified");
42
+ });
43
+
44
+ test("flattenEmptyDirectories collapses a single-subfolder chain into one row", () => {
45
+ // Act - src open but the chain itself collapsed (none of its ids expanded)
46
+ const collapsed = flattenTree(TREE, {
47
+ expandedIds: new Set(["src"]),
48
+ flattenEmptyDirectories: true,
49
+ });
50
+ // Act - expanding via any id along the chain opens it
51
+ const expanded = flattenTree(TREE, {
52
+ expandedIds: new Set(["src", "src/nested/only"]),
53
+ flattenEmptyDirectories: true,
54
+ });
55
+
56
+ // Assert
57
+ expect(collapsed.map((row) => row.label)).toEqual(["src", "a.ts", "nested/only", "README.md"]);
58
+ expect(expanded.map((row) => row.label)).toEqual([
59
+ "src",
60
+ "a.ts",
61
+ "nested/only",
62
+ "deep.ts",
63
+ "README.md",
64
+ ]);
65
+ });
66
+
67
+ test("without the flag the chain stays one folder per row", () => {
68
+ // Act
69
+ const rows = flattenTree(TREE, {
70
+ expandedIds: new Set(["src", "src/nested", "src/nested/only"]),
71
+ });
72
+
73
+ // Assert
74
+ expect(rows.map((row) => [row.label, row.depth])).toEqual([
75
+ ["src", 0],
76
+ ["a.ts", 1],
77
+ ["nested", 1],
78
+ ["only", 2],
79
+ ["deep.ts", 3],
80
+ ["README.md", 0],
81
+ ]);
82
+ });
83
+ });
84
+
85
+ describe("allFolderIds", () => {
86
+ test("collects every folder, not the leaves", () => {
87
+ // Assert
88
+ expect([...allFolderIds(TREE)].sort()).toEqual(["src", "src/nested", "src/nested/only"]);
89
+ });
90
+ });
91
+
92
+ describe("statusMeta", () => {
93
+ test("maps each status to a letter and a tone", () => {
94
+ // Assert
95
+ expect(statusMeta("added")).toEqual({ letter: "A", tone: "green" });
96
+ expect(statusMeta("deleted")).toEqual({ letter: "D", tone: "red" });
97
+ expect(statusMeta("ignored")).toEqual({ letter: "I", tone: "muted" });
98
+ });
99
+ });
@@ -0,0 +1,144 @@
1
+ // Headless tree model mirrored from @pierre/trees (DOM-only): share the model, render our own.
2
+
3
+ export type GitStatus = "added" | "modified" | "deleted" | "renamed" | "untracked" | "ignored";
4
+
5
+ export interface TreeNode {
6
+ id: string;
7
+ label: string;
8
+ children?: TreeNode[];
9
+ status?: GitStatus;
10
+ badge?: string;
11
+ icon?: string;
12
+ }
13
+
14
+ export interface FlattenOptions {
15
+ expandedIds: ReadonlySet<string>;
16
+ flattenEmptyDirectories?: boolean;
17
+ }
18
+
19
+ export interface VisibleRow {
20
+ id: string;
21
+ label: string;
22
+ depth: number;
23
+ isFolder: boolean;
24
+ expanded: boolean;
25
+ status?: GitStatus;
26
+ badge?: string;
27
+ icon?: string;
28
+ }
29
+
30
+ function isFolder(node: TreeNode): boolean {
31
+ return node.children !== undefined;
32
+ }
33
+
34
+ interface CollapsedChain {
35
+ label: string;
36
+ tail: TreeNode;
37
+ chainIds: string[];
38
+ }
39
+
40
+ // flattenEmptyDirectories: fold a chain of single-subfolder folders into one row.
41
+ function collapseChain(node: TreeNode): CollapsedChain {
42
+ let tail = node;
43
+ const labels = [node.label];
44
+ const chainIds = [node.id];
45
+
46
+ while (tail.children?.length === 1 && isFolder(tail.children[0]!)) {
47
+ tail = tail.children[0]!;
48
+ labels.push(tail.label);
49
+ chainIds.push(tail.id);
50
+ }
51
+
52
+ return { label: labels.join("/"), tail, chainIds };
53
+ }
54
+
55
+ export function flattenTree(nodes: readonly TreeNode[], options: FlattenOptions): VisibleRow[] {
56
+ const rows: VisibleRow[] = [];
57
+ const walk = (list: readonly TreeNode[], depth: number): void => {
58
+ for (const node of list) {
59
+ if (!isFolder(node)) {
60
+ const leaf: VisibleRow = {
61
+ id: node.id,
62
+ label: node.label,
63
+ depth,
64
+ isFolder: false,
65
+ expanded: false,
66
+ };
67
+
68
+ if (node.status !== undefined) leaf.status = node.status;
69
+
70
+ if (node.badge !== undefined) leaf.badge = node.badge;
71
+
72
+ if (node.icon !== undefined) leaf.icon = node.icon;
73
+
74
+ rows.push(leaf);
75
+ continue;
76
+ }
77
+
78
+ const collapsed = options.flattenEmptyDirectories
79
+ ? collapseChain(node)
80
+ : { label: node.label, tail: node, chainIds: [node.id] };
81
+ // open when any id along a collapsed chain is expanded
82
+ const expanded = collapsed.chainIds.some((id) => options.expandedIds.has(id));
83
+ const row: VisibleRow = {
84
+ id: node.id,
85
+ label: collapsed.label,
86
+ depth,
87
+ isFolder: true,
88
+ expanded,
89
+ };
90
+
91
+ if (node.badge !== undefined) row.badge = node.badge;
92
+
93
+ if (node.icon !== undefined) row.icon = node.icon;
94
+
95
+ rows.push(row);
96
+
97
+ if (expanded) walk(collapsed.tail.children ?? [], depth + 1);
98
+ }
99
+ };
100
+
101
+ walk(nodes, 0);
102
+
103
+ return rows;
104
+ }
105
+
106
+ export function allFolderIds(nodes: readonly TreeNode[]): Set<string> {
107
+ const ids = new Set<string>();
108
+ const walk = (list: readonly TreeNode[]): void => {
109
+ for (const node of list) {
110
+ if (isFolder(node)) {
111
+ ids.add(node.id);
112
+ walk(node.children ?? []);
113
+ }
114
+ }
115
+ };
116
+
117
+ walk(nodes);
118
+
119
+ return ids;
120
+ }
121
+
122
+ export type TreeTone = "green" | "blue" | "red" | "muted";
123
+
124
+ export interface StatusMeta {
125
+ letter: string;
126
+ tone: TreeTone;
127
+ }
128
+
129
+ export function statusMeta(status: GitStatus): StatusMeta {
130
+ switch (status) {
131
+ case "added":
132
+ return { letter: "A", tone: "green" };
133
+ case "modified":
134
+ return { letter: "M", tone: "blue" };
135
+ case "deleted":
136
+ return { letter: "D", tone: "red" };
137
+ case "renamed":
138
+ return { letter: "R", tone: "blue" };
139
+ case "untracked":
140
+ return { letter: "U", tone: "green" };
141
+ case "ignored":
142
+ return { letter: "I", tone: "muted" };
143
+ }
144
+ }