@cueloop/client 0.1.0-alpha.0 → 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 (345) 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 +41 -27
  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/App.autoclose.test.tsx +144 -0
  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 -80
  333. package/src/App.readonly.test.tsx +0 -148
  334. package/src/App.test.tsx +0 -217
  335. package/src/App.tsx +0 -0
  336. package/src/config.test.ts +0 -116
  337. package/src/config.ts +0 -113
  338. package/src/editor.ts +0 -34
  339. package/src/run.ts +0 -28
  340. package/src/serve.ts +0 -75
  341. package/src/theme.ts +0 -46
  342. package/src/view-diff.test.ts +0 -55
  343. package/src/view-diff.ts +0 -86
  344. package/src/view.test.ts +0 -157
  345. package/src/view.ts +0 -0
@@ -0,0 +1,29 @@
1
+ import { expect, test } from "bun:test";
2
+ import React from "react";
3
+ import { testRender } from "@opentui/react/test-utils";
4
+ import { FileContentsView } from "./FileContentsView";
5
+ import { waitForText } from "../../testing/test-support";
6
+ import { DARK } from "../../appearance/theme";
7
+
8
+ test("renders the loaded file contents", async () => {
9
+ const setup = await testRender(
10
+ <FileContentsView
11
+ path="src/add.ts"
12
+ loadContents={() => Promise.resolve("const answer = 42;\n")}
13
+ theme={DARK}
14
+ />,
15
+ { width: 60, height: 10 },
16
+ );
17
+
18
+ await waitForText(setup, "const answer = 42;");
19
+ });
20
+
21
+ test("shows a centered File deleted message when the file cannot be read", async () => {
22
+ const setup = await testRender(
23
+ <FileContentsView path="missing.ts" loadContents={() => Promise.resolve(null)} theme={DARK} />,
24
+ { width: 60, height: 10 },
25
+ );
26
+
27
+ await waitForText(setup, "File deleted");
28
+ expect(setup.captureCharFrame()).toContain("File deleted");
29
+ });
@@ -0,0 +1,100 @@
1
+ // A Changes/Project file tab in contents mode: a workspace file rendered read-only with line
2
+ // numbers, for surfaces with no thread to anchor notes to (the bare-launch welcome shell).
3
+ // Loads on mount and whenever the path changes; a null read renders a centered "File deleted".
4
+ // Syntax highlighting comes from the native code renderable (tree-sitter), the language
5
+ // auto-detected from the path; unknown languages simply render unstyled.
6
+
7
+ import { ScrollArea } from "../../ui/components/ScrollArea";
8
+ import React, { useEffect, useRef, useState } from "react";
9
+ import type { CodeRenderable } from "@opentui/core";
10
+ import type { Theme } from "../../appearance/theme";
11
+ import { useComponentTheme } from "../../appearance/components/theme-context";
12
+ import { filetypeForPath, syntaxStyleFor } from "../../ui/components/syntax-highlight";
13
+
14
+ export interface FileContentsViewProps {
15
+ path: string;
16
+ loadContents: (path: string) => Promise<string | null>;
17
+ theme?: Theme;
18
+ }
19
+
20
+ /** A finished read for a specific path; `lines` is null when the file could not be read. */
21
+ interface FileLoad {
22
+ path: string;
23
+ lines: string[] | null;
24
+ }
25
+
26
+ export function FileContentsView({
27
+ path,
28
+ loadContents,
29
+ theme,
30
+ }: FileContentsViewProps): React.ReactNode {
31
+ const tokens = useComponentTheme(theme);
32
+ const [loaded, setLoaded] = useState<FileLoad | null>(null);
33
+ // The gutter mirrors the code renderable's line info, so it needs the mounted
34
+ // instance; a state-backed ref rebinds the target once the code renderable exists.
35
+ const [codeTarget, setCodeTarget] = useState<CodeRenderable | null>(null);
36
+ const loadRef = useRef(loadContents);
37
+
38
+ useEffect(() => {
39
+ loadRef.current = loadContents;
40
+ });
41
+
42
+ useEffect(() => {
43
+ let alive = true;
44
+
45
+ void loadRef.current(path).then(
46
+ (contents) => {
47
+ if (alive) setLoaded({ path, lines: contents === null ? null : contents.split("\n") });
48
+ },
49
+ () => {
50
+ if (alive) setLoaded({ path, lines: null });
51
+ },
52
+ );
53
+
54
+ return () => {
55
+ alive = false;
56
+ };
57
+ }, [path]);
58
+
59
+ if (loaded === null || loaded.path !== path) {
60
+ return (
61
+ <box style={{ flexGrow: 1, paddingLeft: 1, paddingTop: 1 }}>
62
+ <text fg={tokens.textDim}>loading...</text>
63
+ </box>
64
+ );
65
+ }
66
+ if (loaded.lines === null) {
67
+ // a file that no longer reads in the Changes/Project view is one the working tree deleted;
68
+ // the bottom pad lifts the text one row so it lines up with the footer-shortened thread empty state
69
+ return (
70
+ <box
71
+ style={{ flexGrow: 1, alignItems: "center", justifyContent: "center", paddingBottom: 2 }}
72
+ >
73
+ <text fg={tokens.textDim}>File deleted</text>
74
+ </box>
75
+ );
76
+ }
77
+ const content = loaded.lines.join("\n");
78
+
79
+ return (
80
+ <ScrollArea>
81
+ <line-number
82
+ target={codeTarget ?? undefined}
83
+ showLineNumbers
84
+ fg={tokens.textDim}
85
+ minWidth={5}
86
+ paddingRight={1}
87
+ style={{ paddingTop: 1, paddingLeft: 1 }}
88
+ >
89
+ <code
90
+ ref={setCodeTarget}
91
+ content={content}
92
+ filetype={filetypeForPath(path)}
93
+ syntaxStyle={syntaxStyleFor(tokens)}
94
+ selectable={false}
95
+ style={{ wrapMode: "none", fg: tokens.text }}
96
+ />
97
+ </line-number>
98
+ </ScrollArea>
99
+ );
100
+ }
@@ -0,0 +1,151 @@
1
+ /** File tabs keep path actions while only the aggregate Changes tab can fold a file. */
2
+
3
+ import { expect, test } from "bun:test";
4
+ import React from "react";
5
+ import { testRender } from "@opentui/react/test-utils";
6
+ import { changesTab, fileTab } from "./editor-grid";
7
+ import { GridTabContent, type DiffSurfaceProps } from "./GridTabContent";
8
+ import { FIXTURE_PATCH, fixtureDiffSession } from "../../stories/story-fixtures";
9
+ import { NERD } from "../../ui/components/primitives/icons";
10
+ import { TooltipProvider } from "../../ui/components/Tooltip";
11
+ import { locateText, waitForText } from "../../testing/test-support";
12
+ import { DARK } from "../../appearance/theme";
13
+ import { diffRows } from "../../diff/view-diff";
14
+
15
+ const noop = (): void => {};
16
+ const rows = diffRows(
17
+ FIXTURE_PATCH.replaceAll("src/store.ts", "src/store.txt").replace("-1,4 +1,4", "-1,3 +1,3"),
18
+ );
19
+ const surface: DiffSurfaceProps = {
20
+ session: fixtureDiffSession(),
21
+ quickActions: [],
22
+ observer: false,
23
+ onAnnotate: noop,
24
+ onReply: noop,
25
+ onUpdateAnnotation: noop,
26
+ onExit: noop,
27
+ };
28
+
29
+ test("a single-file diff keeps copy and expand without a collapse action", async () => {
30
+ const copied: string[] = [];
31
+ const setup = await testRender(
32
+ <TooltipProvider theme={DARK}>
33
+ <GridTabContent
34
+ tab={fileTab("store.txt", "src/store.txt", "diff")}
35
+ rows={rows}
36
+ surface={surface}
37
+ rejectedRows={new Set()}
38
+ fold={{
39
+ isCollapsed: () => false,
40
+ isExpanded: () => false,
41
+ canExpand: () => true,
42
+ onToggleCollapse: noop,
43
+ onToggleExpand: noop,
44
+ onCopyPath: (file) => {
45
+ copied.push(file);
46
+
47
+ return true;
48
+ },
49
+ }}
50
+ dimmed={false}
51
+ readFile={async () => null}
52
+ onAddFileComment={noop}
53
+ theme={DARK}
54
+ />
55
+ </TooltipProvider>,
56
+ { width: 80, height: 12 },
57
+ );
58
+
59
+ await waitForText(setup, "src/store.txt");
60
+ const frame = setup.captureCharFrame();
61
+
62
+ expect(frame).toContain(NERD.copy);
63
+ expect(frame).not.toContain("v src/store.txt");
64
+ expect(frame).toContain("expand");
65
+ const copy = locateText(setup, NERD.copy);
66
+
67
+ await setup.mockMouse.moveTo(copy.column, copy.row);
68
+ await waitForText(setup, "Copy path");
69
+ await setup.mockMouse.click(copy.column, copy.row);
70
+ expect(copied).toEqual(["src/store.txt"]);
71
+ await waitForText(setup, "copied");
72
+ await setup.waitForVisualIdle();
73
+ setup.renderer.destroy();
74
+ });
75
+
76
+ test("the full Changes tab retains fold and expand controls", async () => {
77
+ const setup = await testRender(
78
+ <GridTabContent
79
+ tab={changesTab()}
80
+ rows={rows}
81
+ surface={surface}
82
+ rejectedRows={new Set()}
83
+ fold={{
84
+ isCollapsed: () => false,
85
+ isExpanded: () => false,
86
+ canExpand: () => true,
87
+ onToggleCollapse: noop,
88
+ onToggleExpand: noop,
89
+ onCopyPath: () => true,
90
+ }}
91
+ dimmed={false}
92
+ readFile={async () => null}
93
+ onAddFileComment={noop}
94
+ theme={DARK}
95
+ />,
96
+ { width: 80, height: 12 },
97
+ );
98
+
99
+ await waitForText(setup, "src/store.txt");
100
+ const frame = setup.captureCharFrame();
101
+
102
+ expect(frame).toContain("src/store.txt");
103
+ expect(frame).not.toContain("v src/store.txt");
104
+ expect(frame).toContain(NERD.copy);
105
+ expect(frame).toContain("expand");
106
+ await setup.waitForVisualIdle();
107
+ setup.renderer.destroy();
108
+ });
109
+
110
+ test("the file title tooltip follows its collapse action", async () => {
111
+ function TestChanges(): React.ReactNode {
112
+ const [collapsed, setCollapsed] = React.useState(false);
113
+
114
+ return (
115
+ <TooltipProvider theme={DARK}>
116
+ <GridTabContent
117
+ tab={changesTab()}
118
+ rows={rows}
119
+ surface={surface}
120
+ rejectedRows={new Set()}
121
+ fold={{
122
+ isCollapsed: () => collapsed,
123
+ isExpanded: () => false,
124
+ canExpand: () => true,
125
+ onToggleCollapse: () => setCollapsed((current) => !current),
126
+ onToggleExpand: noop,
127
+ onCopyPath: () => true,
128
+ }}
129
+ dimmed={false}
130
+ readFile={async () => null}
131
+ onAddFileComment={noop}
132
+ theme={DARK}
133
+ />
134
+ </TooltipProvider>
135
+ );
136
+ }
137
+
138
+ const setup = await testRender(<TestChanges />, { width: 80, height: 12 });
139
+
140
+ await waitForText(setup, "src/store.txt");
141
+ const title = locateText(setup, "src/store.txt");
142
+
143
+ await setup.mockMouse.moveTo(title.column, title.row);
144
+ await waitForText(setup, "collapse");
145
+ await setup.mockMouse.click(title.column, title.row);
146
+ await waitForText(setup, "uncollapse");
147
+ await setup.mockMouse.click(title.column, title.row);
148
+ await waitForText(setup, "collapse");
149
+ expect(setup.captureCharFrame()).not.toContain("uncollapse");
150
+ setup.renderer.destroy();
151
+ });
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import { DEFAULT_QUICK_ACTIONS } from "../../settings/config";
4
+ import type { Story, StoryMeta } from "../../stories/story";
5
+ import { GridTabContent, type DiffSurfaceProps } from "./GridTabContent";
6
+ import { fixtureDiffRows, fixtureDiffSession } from "../../stories/story-fixtures";
7
+ import { changesTab, fileTab } from "./editor-grid";
8
+
9
+ export const meta: StoryMeta = { title: "Surfaces/GridTabContent" };
10
+
11
+ const noop = (): void => {};
12
+
13
+ function surface(): DiffSurfaceProps {
14
+ return {
15
+ session: fixtureDiffSession({ annotations: [] }),
16
+ quickActions: DEFAULT_QUICK_ACTIONS,
17
+ observer: false,
18
+ onAnnotate: noop,
19
+ onReply: noop,
20
+ onUpdateAnnotation: noop,
21
+ onExit: noop,
22
+ };
23
+ }
24
+
25
+ /** The whole-diff Changes tab: every changed file in one scroll container. */
26
+ export const ChangesTab: Story = {
27
+ render: () => (
28
+ <GridTabContent
29
+ tab={changesTab()}
30
+ rows={fixtureDiffRows()}
31
+ surface={surface()}
32
+ rejectedRows={new Set()}
33
+ dimmed={false}
34
+ readFile={async () => null}
35
+ onAddFileComment={noop}
36
+ theme={DARK}
37
+ />
38
+ ),
39
+ expectedColors: [DARK.textMuted],
40
+ size: { width: 80, height: 18 },
41
+ };
42
+
43
+ /** A single-file diff tab: that file's rows alone, indexed on their own. */
44
+ export const FileDiffTab: Story = {
45
+ render: () => (
46
+ <GridTabContent
47
+ tab={fileTab("store.ts", "src/store.ts", "diff")}
48
+ rows={fixtureDiffRows()}
49
+ surface={surface()}
50
+ rejectedRows={new Set()}
51
+ dimmed={false}
52
+ readFile={async () => null}
53
+ onAddFileComment={noop}
54
+ theme={DARK}
55
+ />
56
+ ),
57
+ expectedColors: [DARK.textMuted],
58
+ size: { width: 80, height: 18 },
59
+ };
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The body an editor tab paints: the whole working-tree/artifact diff for the
3
+ * Changes tab, or a single file's diff (or read-only contents) for a file tab.
4
+ * Shared by the thread workbench and the bare-launch shell so both render a
5
+ * changed file the same way, with comments routed back through one surface.
6
+ */
7
+
8
+ import React, { useMemo } from "react";
9
+ import { type Anchor } from "@cueloop/schema";
10
+ import { dimmedTheme, type Theme } from "../../appearance/theme";
11
+ import { changesMarks, type DiffRow } from "../../diff/view-diff";
12
+ import {
13
+ DiffContentView,
14
+ type DiffFoldControls,
15
+ type DiffContentViewProps,
16
+ } from "../../diff/components/DiffContentView";
17
+ import { AnnotatableFileView } from "../../diff/components/AnnotatableFileView";
18
+ import type { EditorTab } from "./editor-grid";
19
+
20
+ /** The inline-commenting props the diff sheet shares with the thread view, wired once by the app. */
21
+ export type DiffSurfaceProps = Pick<
22
+ DiffContentViewProps,
23
+ | "session"
24
+ | "quickActions"
25
+ | "observer"
26
+ | "commentsEnabled"
27
+ | "resolved"
28
+ | "suspended"
29
+ | "onComposingChange"
30
+ | "onObserverBlocked"
31
+ | "onCursorChange"
32
+ | "focusedAnnotationId"
33
+ | "onFocusAnnotation"
34
+ | "onAnnotate"
35
+ | "onReply"
36
+ | "onUpdateAnnotation"
37
+ | "isAnnotationReadOnly"
38
+ | "annotationAction"
39
+ | "onInvoke"
40
+ | "resolveAuthorLabel"
41
+ | "onNavCommand"
42
+ | "onExit"
43
+ >;
44
+
45
+ /** The Changes tab body: the whole diff in one scroll container, or a bare hint when nothing changed. */
46
+ export function ChangesTabBody(props: {
47
+ rows: DiffRow[];
48
+ surface: DiffSurfaceProps;
49
+ rejectedRows: Set<number>;
50
+ fold?: DiffFoldControls;
51
+ showFileCollapse?: boolean;
52
+ fileStats?: ReadonlyMap<string, { additions: number; deletions: number }>;
53
+ split?: boolean;
54
+ dimmed: boolean;
55
+ /** Rows the sibling Thread pane spends on its footer, so this empty hint centers level with it. */
56
+ emptyBottomPadding?: number;
57
+ theme: Theme;
58
+ }): React.ReactNode {
59
+ const session = props.surface.session;
60
+ const marks = useMemo(
61
+ () => changesMarks(session, props.rows, props.surface.focusedAnnotationId),
62
+ [session, props.rows, props.surface.focusedAnnotationId],
63
+ );
64
+
65
+ if (props.rows.length === 0) {
66
+ return (
67
+ <box
68
+ style={{
69
+ flexGrow: 1,
70
+ flexDirection: "column",
71
+ alignItems: "center",
72
+ justifyContent: "center",
73
+ paddingBottom: props.emptyBottomPadding,
74
+ }}
75
+ >
76
+ <text fg={props.theme.textDim}>No changes</text>
77
+ </box>
78
+ );
79
+ }
80
+
81
+ return (
82
+ <DiffContentView
83
+ rows={props.rows}
84
+ marks={marks}
85
+ {...props.surface}
86
+ rejectedRows={props.rejectedRows}
87
+ fold={props.fold}
88
+ showFileCollapse={props.showFileCollapse}
89
+ fileStats={props.fileStats}
90
+ split={props.split}
91
+ theme={props.dimmed ? dimmedTheme(props.theme) : undefined}
92
+ />
93
+ );
94
+ }
95
+
96
+ /** An editor tab's body: the whole diff for the Changes tab, a file's diff or contents for a file tab. */
97
+ export function GridTabContent(props: {
98
+ tab: EditorTab;
99
+ rows: DiffRow[];
100
+ surface: DiffSurfaceProps;
101
+ rejectedRows: Set<number>;
102
+ fold?: DiffFoldControls;
103
+ fileStats?: ReadonlyMap<string, { additions: number; deletions: number }>;
104
+ split?: boolean;
105
+ dimmed: boolean;
106
+ readFile: (path: string) => Promise<string | null>;
107
+ /** Persist a comment on a project file; the file view built the anchor against its own lines. */
108
+ onAddFileComment: (path: string, anchor: Anchor, body: string) => void;
109
+ /** Rows the sibling Thread pane spends on its footer, so the empty hint centers level with it. */
110
+ emptyBottomPadding?: number;
111
+ theme: Theme;
112
+ }): React.ReactNode {
113
+ const { tab } = props;
114
+
115
+ if (tab.kind === "file" && tab.fileView === "contents" && tab.path !== undefined) {
116
+ const filePath = tab.path;
117
+
118
+ return (
119
+ <AnnotatableFileView
120
+ path={filePath}
121
+ loadContents={props.readFile}
122
+ session={props.surface.session}
123
+ quickActions={props.surface.quickActions}
124
+ observer={props.surface.observer}
125
+ resolved={props.surface.resolved}
126
+ suspended={props.surface.suspended}
127
+ focusedAnnotationId={props.surface.focusedAnnotationId}
128
+ onFocusAnnotation={props.surface.onFocusAnnotation}
129
+ onComposingChange={props.surface.onComposingChange}
130
+ onCursorChange={props.surface.onCursorChange}
131
+ onObserverBlocked={props.surface.onObserverBlocked}
132
+ onAddComment={(anchor, body) => props.onAddFileComment(filePath, anchor, body)}
133
+ onReply={props.surface.onReply}
134
+ onUpdateAnnotation={props.surface.onUpdateAnnotation}
135
+ isAnnotationReadOnly={props.surface.isAnnotationReadOnly}
136
+ annotationAction={props.surface.annotationAction}
137
+ onInvoke={props.surface.onInvoke}
138
+ resolveAuthorLabel={props.surface.resolveAuthorLabel}
139
+ onExit={props.surface.onExit}
140
+ theme={props.theme}
141
+ />
142
+ );
143
+ }
144
+ if (tab.kind !== "file") {
145
+ return (
146
+ <ChangesTabBody
147
+ rows={props.rows}
148
+ surface={props.surface}
149
+ rejectedRows={props.rejectedRows}
150
+ fold={props.fold}
151
+ fileStats={props.fileStats}
152
+ split={props.split}
153
+ dimmed={props.dimmed}
154
+ emptyBottomPadding={props.emptyBottomPadding}
155
+ theme={props.theme}
156
+ />
157
+ );
158
+ }
159
+ // a single-file tab shows that file's rows alone, so its row indices are its own: comments and
160
+ // the caret report back in whole-diff indices; a single-file tab omits only the collapse action
161
+ const fileRowIndices = props.rows.flatMap((row, index) => (row.file === tab.path ? [index] : []));
162
+ const rows = fileRowIndices.map((index) => props.rows[index]!);
163
+ const wholeIndex = (rowIndex: number): number => fileRowIndices[rowIndex] ?? rowIndex;
164
+ const rejectedRows = new Set(
165
+ fileRowIndices.flatMap((index, rowIndex) => (props.rejectedRows.has(index) ? [rowIndex] : [])),
166
+ );
167
+
168
+ return (
169
+ <ChangesTabBody
170
+ rows={rows}
171
+ surface={{
172
+ ...props.surface,
173
+ onCursorChange: (rowIndex) => props.surface.onCursorChange?.(wholeIndex(rowIndex)),
174
+ onAnnotate: (span, body) =>
175
+ props.surface.onAnnotate(
176
+ {
177
+ start: { blockIndex: wholeIndex(span.start.blockIndex), char: span.start.char },
178
+ end: { blockIndex: wholeIndex(span.end.blockIndex), char: span.end.char },
179
+ },
180
+ body,
181
+ ),
182
+ }}
183
+ rejectedRows={rejectedRows}
184
+ fileStats={props.fileStats}
185
+ fold={props.fold}
186
+ showFileCollapse={false}
187
+ split={props.split}
188
+ dimmed={props.dimmed}
189
+ emptyBottomPadding={props.emptyBottomPadding}
190
+ theme={props.theme}
191
+ />
192
+ );
193
+ }
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../stories/story";
4
+ import { ProjectTreeView } from "./ProjectTreeView";
5
+
6
+ export const meta: StoryMeta = { title: "Surfaces/ProjectTreeView" };
7
+
8
+ const SAMPLE = [
9
+ "packages/client/src/App.tsx",
10
+ "packages/client/src/components/AppShell.tsx",
11
+ "packages/daemon/src/api.ts",
12
+ "README.md",
13
+ ];
14
+
15
+ export const Loaded: Story = {
16
+ render: () => (
17
+ <ProjectTreeView
18
+ loadFiles={() => Promise.resolve(SAMPLE)}
19
+ onSelectFile={() => {}}
20
+ theme={DARK}
21
+ />
22
+ ),
23
+ expectedColors: [DARK.textDim],
24
+ size: { width: 40, height: 16 },
25
+ };