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

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 (356) 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 +369 -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 +1243 -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 +20 -0
  36. package/src/app/App.toast.test.tsx +124 -0
  37. package/src/app/App.tsx +1667 -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.agent.test.tsx +50 -0
  119. package/src/integrations/components/ShareDialog.stories.tsx +121 -0
  120. package/src/integrations/components/ShareDialog.tsx +574 -0
  121. package/src/integrations/components/share-dialog-store.ts +59 -0
  122. package/src/integrations/editor.test.ts +149 -0
  123. package/src/integrations/editor.ts +160 -0
  124. package/src/integrations/github-identity.test.ts +58 -0
  125. package/src/integrations/github-identity.ts +41 -0
  126. package/src/integrations/herdr.test.ts +43 -0
  127. package/src/integrations/herdr.ts +38 -0
  128. package/src/integrations/integrations.test.ts +41 -0
  129. package/src/integrations/integrations.ts +32 -0
  130. package/src/integrations/pasted-image.test.ts +67 -0
  131. package/src/integrations/pasted-image.ts +60 -0
  132. package/src/integrations/share.ts +238 -0
  133. package/src/keyboard/components/CompletionOverlay.stories.tsx +37 -0
  134. package/src/keyboard/components/CompletionOverlay.tsx +75 -0
  135. package/src/keyboard/components/HandleChipsInput.stories.tsx +24 -0
  136. package/src/keyboard/components/HandleChipsInput.test.tsx +35 -0
  137. package/src/keyboard/components/HandleChipsInput.tsx +143 -0
  138. package/src/keyboard/components/KeybindsDialog.stories.tsx +31 -0
  139. package/src/keyboard/components/KeybindsDialog.tsx +103 -0
  140. package/src/keyboard/components/NavModeHint.stories.tsx +40 -0
  141. package/src/keyboard/components/NavModeHint.tsx +31 -0
  142. package/src/keyboard/components/SlashComposer.stories.tsx +37 -0
  143. package/src/keyboard/components/SlashComposer.tsx +87 -0
  144. package/src/keyboard/key-bindings.test.ts +156 -0
  145. package/src/keyboard/key-bindings.ts +459 -0
  146. package/src/keyboard/keymap.test.ts +611 -0
  147. package/src/keyboard/keymap.ts +395 -0
  148. package/src/keyboard/skills.test.ts +74 -0
  149. package/src/keyboard/skills.ts +75 -0
  150. package/src/keyboard/slash-palette.test.ts +103 -0
  151. package/src/keyboard/slash-palette.ts +129 -0
  152. package/src/keyboard/thread-chords.test.ts +79 -0
  153. package/src/keyboard/thread-chords.ts +163 -0
  154. package/src/keyboard/use-shared-keyboard.ts +55 -0
  155. package/src/markdown/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  156. package/src/markdown/components/MarkdownGridBlock.stories.tsx +33 -0
  157. package/src/markdown/components/MarkdownGridBlock.tsx +72 -0
  158. package/src/markdown/components/MarkdownThreadEditor.stories.tsx +15 -0
  159. package/src/markdown/components/MarkdownThreadEditor.test.tsx +98 -0
  160. package/src/markdown/components/MarkdownThreadEditor.tsx +160 -0
  161. package/src/markdown/components/ThreadView.selection.test.tsx +951 -0
  162. package/src/markdown/components/ThreadView.slash.test.tsx +146 -0
  163. package/src/markdown/components/ThreadView.stories.tsx +26 -0
  164. package/src/markdown/components/ThreadView.tsx +560 -0
  165. package/src/markdown/mark-runs.ts +121 -0
  166. package/src/markdown/markdown-editor-syntax-style.ts +50 -0
  167. package/src/markdown/markdown-grid.test.ts +101 -0
  168. package/src/markdown/markdown-grid.ts +203 -0
  169. package/src/markdown/markdown-highlight.test.ts +61 -0
  170. package/src/markdown/markdown-highlight.ts +124 -0
  171. package/src/markdown/thread-code-syntax.ts +45 -0
  172. package/src/markdown/use-thread-body-editing.tsx +54 -0
  173. package/src/markdown/view-plan.regression.test.ts +60 -0
  174. package/src/markdown/view-plan.test.ts +545 -0
  175. package/src/markdown/view-plan.ts +1009 -0
  176. package/src/performance/otlp-trace.test.ts +119 -0
  177. package/src/performance/otlp-trace.ts +134 -0
  178. package/src/performance/perf-report.ts +18 -0
  179. package/src/performance/perf-timings.test.ts +18 -0
  180. package/src/performance/perf-timings.ts +39 -0
  181. package/src/prototype/components/PrototypeContentView.stories.tsx +19 -0
  182. package/src/prototype/components/PrototypeContentView.tsx +442 -0
  183. package/src/prototype/prototype-browser.test.ts +81 -0
  184. package/src/prototype/prototype-browser.ts +321 -0
  185. package/src/prototype/prototype-pixels.tsx +35 -0
  186. package/src/settings/components/SettingsDialog.stories.tsx +117 -0
  187. package/src/settings/components/SettingsDialog.tsx +183 -0
  188. package/src/settings/components/SettingsRows.stories.tsx +18 -0
  189. package/src/settings/components/SettingsRows.tsx +112 -0
  190. package/src/settings/components/quick-actions-editor.stories.tsx +58 -0
  191. package/src/settings/components/quick-actions-editor.test.tsx +120 -0
  192. package/src/settings/components/quick-actions-editor.tsx +222 -0
  193. package/src/settings/config-identity.test.ts +78 -0
  194. package/src/settings/config.test.ts +676 -0
  195. package/src/settings/config.ts +628 -0
  196. package/src/settings/use-settings-dialog.tsx +351 -0
  197. package/src/stories/__snapshots__/stories.test.tsx.snap +2358 -0
  198. package/src/stories/stories-app.tsx +196 -0
  199. package/src/stories/stories.test.tsx +74 -0
  200. package/src/stories/story-fixtures.ts +102 -0
  201. package/src/stories/story-tree.test.ts +108 -0
  202. package/src/stories/story.ts +154 -0
  203. package/src/terminal/ansi-screen.test.ts +28 -0
  204. package/src/terminal/ansi-screen.ts +72 -0
  205. package/src/terminal/components/AnsiScreen.stories.tsx +12 -0
  206. package/src/terminal/components/AnsiScreen.tsx +28 -0
  207. package/src/terminal/components/terminal-pane.test.tsx +40 -0
  208. package/src/terminal/components/terminal-pane.ts +196 -0
  209. package/src/terminal/ghostty-terminal.test.ts +85 -0
  210. package/src/terminal/ghostty-terminal.ts +197 -0
  211. package/src/terminal/kitty-image.test.ts +66 -0
  212. package/src/terminal/kitty-image.ts +137 -0
  213. package/src/terminal/pty.test.ts +81 -0
  214. package/src/terminal/pty.ts +246 -0
  215. package/src/testing/test-support.test.ts +264 -0
  216. package/src/testing/test-support.ts +367 -0
  217. package/src/thread/agent-continuation.test.ts +32 -0
  218. package/src/thread/agent-continuation.ts +13 -0
  219. package/src/thread/agent-transcript.test.ts +161 -0
  220. package/src/thread/agent-transcript.ts +336 -0
  221. package/src/thread/components/AgentConfigControls.stories.tsx +39 -0
  222. package/src/thread/components/AgentConfigControls.test.tsx +112 -0
  223. package/src/thread/components/AgentConfigControls.tsx +114 -0
  224. package/src/thread/components/AgentThreadPane.stories.tsx +105 -0
  225. package/src/thread/components/AgentThreadPane.test.tsx +935 -0
  226. package/src/thread/components/AgentThreadPane.tsx +377 -0
  227. package/src/thread/components/ThreadFooter.stories.tsx +30 -0
  228. package/src/thread/components/ThreadFooter.test.tsx +49 -0
  229. package/src/thread/components/ThreadFooter.tsx +77 -0
  230. package/src/thread/components/ThreadTree.stories.tsx +46 -0
  231. package/src/thread/components/ThreadTree.test.tsx +41 -0
  232. package/src/thread/components/ThreadTree.tsx +267 -0
  233. package/src/thread/components/thread-tree.test.ts +137 -0
  234. package/src/thread/components/thread-tree.ts +112 -0
  235. package/src/thread/thread-controller.curate.test.ts +502 -0
  236. package/src/thread/thread-controller.derivation.test.ts +62 -0
  237. package/src/thread/thread-controller.live-diff.test.ts +357 -0
  238. package/src/thread/thread-controller.optimistic.test.ts +69 -0
  239. package/src/thread/thread-controller.share.test.ts +573 -0
  240. package/src/thread/thread-controller.switch-cache.test.ts +174 -0
  241. package/src/thread/thread-controller.tree.test.ts +263 -0
  242. package/src/thread/thread-controller.ts +2026 -0
  243. package/src/thread/tree-view.test.ts +128 -0
  244. package/src/thread/tree-view.ts +146 -0
  245. package/src/thread/use-thread-agent.test.tsx +208 -0
  246. package/src/thread/use-thread-agent.ts +117 -0
  247. package/src/ui/components/CodeBlock.stories.tsx +42 -0
  248. package/src/ui/components/CodeBlock.tsx +80 -0
  249. package/src/ui/components/ConfirmCard.stories.tsx +37 -0
  250. package/src/ui/components/ConfirmCard.tsx +151 -0
  251. package/src/ui/components/ConfirmDialog.stories.tsx +21 -0
  252. package/src/ui/components/ConfirmDialog.tsx +72 -0
  253. package/src/ui/components/OverlayScrollbar.stories.tsx +33 -0
  254. package/src/ui/components/OverlayScrollbar.test.tsx +139 -0
  255. package/src/ui/components/OverlayScrollbar.tsx +129 -0
  256. package/src/ui/components/PromptDialog.stories.tsx +36 -0
  257. package/src/ui/components/PromptDialog.test.tsx +65 -0
  258. package/src/ui/components/PromptDialog.tsx +99 -0
  259. package/src/ui/components/RootOverlay.stories.tsx +45 -0
  260. package/src/ui/components/RootOverlay.tsx +49 -0
  261. package/src/ui/components/ScrollArea.stories.tsx +20 -0
  262. package/src/ui/components/ScrollArea.tsx +61 -0
  263. package/src/ui/components/Toast.stories.tsx +19 -0
  264. package/src/ui/components/Toast.tsx +54 -0
  265. package/src/ui/components/Tooltip.stories.tsx +26 -0
  266. package/src/ui/components/Tooltip.tsx +72 -0
  267. package/src/ui/components/menu-control.stories.tsx +35 -0
  268. package/src/ui/components/menu-control.tsx +43 -0
  269. package/src/ui/components/primitives/Button.stories.tsx +32 -0
  270. package/src/ui/components/primitives/Button.tsx +57 -0
  271. package/src/ui/components/primitives/Card.stories.tsx +25 -0
  272. package/src/ui/components/primitives/Card.tsx +62 -0
  273. package/src/ui/components/primitives/Dialog.stories.tsx +24 -0
  274. package/src/ui/components/primitives/Dialog.test.tsx +38 -0
  275. package/src/ui/components/primitives/Dialog.tsx +69 -0
  276. package/src/ui/components/primitives/DialogActions.stories.tsx +20 -0
  277. package/src/ui/components/primitives/DialogActions.tsx +39 -0
  278. package/src/ui/components/primitives/IconButton.stories.tsx +36 -0
  279. package/src/ui/components/primitives/IconButton.tsx +58 -0
  280. package/src/ui/components/primitives/StatusBar.stories.tsx +11 -0
  281. package/src/ui/components/primitives/StatusBar.tsx +23 -0
  282. package/src/ui/components/primitives/Switch.stories.tsx +17 -0
  283. package/src/ui/components/primitives/Switch.tsx +32 -0
  284. package/src/ui/components/primitives/Tabs.stories.tsx +29 -0
  285. package/src/ui/components/primitives/Tabs.tsx +101 -0
  286. package/src/ui/components/primitives/Toolbar.stories.tsx +17 -0
  287. package/src/ui/components/primitives/Toolbar.tsx +14 -0
  288. package/src/ui/components/primitives/Tree.stories.tsx +124 -0
  289. package/src/ui/components/primitives/Tree.tsx +133 -0
  290. package/src/ui/components/primitives/frame.test.ts +12 -0
  291. package/src/ui/components/primitives/frame.ts +17 -0
  292. package/src/ui/components/primitives/icons.ts +61 -0
  293. package/src/ui/components/primitives/tree-model.test.ts +99 -0
  294. package/src/ui/components/primitives/tree-model.ts +137 -0
  295. package/src/ui/components/syntax-highlight.ts +133 -0
  296. package/src/ui/components/truncate-text.ts +7 -0
  297. package/src/ui/components/truncate-title.test.ts +31 -0
  298. package/src/ui/components/truncate-title.ts +15 -0
  299. package/src/ui/use-frame-measure.ts +74 -0
  300. package/src/ui/use-terminal-virtualizer.ts +259 -0
  301. package/src/workbench/components/ChangesColumn.stories.tsx +44 -0
  302. package/src/workbench/components/ChangesColumn.tsx +203 -0
  303. package/src/workbench/components/ChangesFileTree.test.tsx +181 -0
  304. package/src/workbench/components/EditorGrid.split.test.tsx +118 -0
  305. package/src/workbench/components/EditorGrid.stories.tsx +86 -0
  306. package/src/workbench/components/EditorGrid.tabs.test.tsx +118 -0
  307. package/src/workbench/components/EditorGrid.tsx +544 -0
  308. package/src/workbench/components/EditorGrid.zoom.test.tsx +132 -0
  309. package/src/workbench/components/FileContentsView.stories.tsx +16 -0
  310. package/src/workbench/components/FileContentsView.test.tsx +29 -0
  311. package/src/workbench/components/FileContentsView.tsx +100 -0
  312. package/src/workbench/components/GridTabContent.actions.test.tsx +151 -0
  313. package/src/workbench/components/GridTabContent.stories.tsx +59 -0
  314. package/src/workbench/components/GridTabContent.tsx +193 -0
  315. package/src/workbench/components/ProjectTreeView.stories.tsx +25 -0
  316. package/src/workbench/components/ProjectTreeView.test.tsx +236 -0
  317. package/src/workbench/components/ProjectTreeView.tsx +151 -0
  318. package/src/workbench/components/TreePane.stories.tsx +106 -0
  319. package/src/workbench/components/TreePane.tsx +155 -0
  320. package/src/workbench/components/WelcomePlayground.stories.tsx +12 -0
  321. package/src/workbench/components/WelcomePlayground.test.tsx +83 -0
  322. package/src/workbench/components/WelcomePlayground.tsx +138 -0
  323. package/src/workbench/components/editor-grid.test.ts +184 -0
  324. package/src/workbench/components/editor-grid.ts +281 -0
  325. package/src/workbench/components/file-tree.test.ts +60 -0
  326. package/src/workbench/components/file-tree.ts +112 -0
  327. package/src/workbench/use-changes-workbench.ts +205 -0
  328. package/src/workbench/use-remember-layout.ts +31 -0
  329. package/src/workbench/use-repo-changes.ts +91 -0
  330. package/src/workbench/workbench-snapshot.test.ts +99 -0
  331. package/src/workbench/workbench-snapshot.ts +59 -0
  332. package/src/App.diff.test.tsx +0 -99
  333. package/src/App.export.test.tsx +0 -88
  334. package/src/App.plan-review.test.tsx +0 -259
  335. package/src/App.readonly.test.tsx +0 -148
  336. package/src/App.rendering.test.tsx +0 -108
  337. package/src/App.submit-confirm.test.tsx +0 -183
  338. package/src/App.test.tsx +0 -218
  339. package/src/App.tsx +0 -1249
  340. package/src/config.test.ts +0 -116
  341. package/src/config.ts +0 -150
  342. package/src/editor.ts +0 -34
  343. package/src/herdr.test.ts +0 -68
  344. package/src/herdr.ts +0 -34
  345. package/src/keymap.test.ts +0 -270
  346. package/src/keymap.ts +0 -173
  347. package/src/run.ts +0 -30
  348. package/src/serve.ts +0 -75
  349. package/src/session-controller.ts +0 -398
  350. package/src/syntax.test.ts +0 -41
  351. package/src/syntax.ts +0 -88
  352. package/src/theme.ts +0 -46
  353. package/src/view-diff.test.ts +0 -55
  354. package/src/view-diff.ts +0 -86
  355. package/src/view.test.ts +0 -157
  356. package/src/view.ts +0 -364
@@ -1,108 +0,0 @@
1
- /** Plan rendering (#67): code blocks verbatim + contained, spacing never collapses. */
2
-
3
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
4
- import { mkdtempSync, rmSync } from "node:fs";
5
- import { tmpdir } from "node:os";
6
- import { join } from "node:path";
7
- import React from "react";
8
- import { testRender } from "@opentui/react/test-utils";
9
- import { DaemonServer } from "@cueloop/daemon";
10
- import type { ReviewSession } from "@cueloop/schema";
11
- import { App } from "./App";
12
-
13
- const PLAN = `# Render Plan
14
-
15
- ## Sensor
16
-
17
- - wire the duck over MQTT
18
-
19
- \`\`\`tsx
20
- export function Gate({ full }: { full: number }) {
21
- const blocked = full < 0.6;
22
- return <text>{blocked ? "blocked" : "clear"}</text>;
23
- }
24
- \`\`\`
25
-
26
- ## Gate
27
-
28
- Deploys block below the threshold.
29
- `;
30
-
31
- let home: string;
32
- let server: DaemonServer;
33
- let session: ReviewSession;
34
-
35
- beforeEach(() => {
36
- home = mkdtempSync(join(tmpdir(), "cueloop-render-"));
37
- server = new DaemonServer({ home, idleExitMs: 0 });
38
- server.start();
39
- session = server.core.sessionCreate({
40
- workspace: { repoRoot: "/repo", branch: "main" },
41
- artifact: { type: "plan", content: PLAN, meta: { title: "Render Plan" } },
42
- });
43
- });
44
- afterEach(() => {
45
- server.stop();
46
- rmSync(home, { recursive: true, force: true });
47
- });
48
-
49
- async function frame(): Promise<string> {
50
- const setup = await testRender(<App home={home} sessionId={session.id} />, { width: 120, height: 36 });
51
- const deadline = Date.now() + 10_000;
52
- while (Date.now() < deadline && !setup.captureCharFrame().includes("Render Plan")) {
53
- await Bun.sleep(25);
54
- await setup.renderOnce();
55
- }
56
- // give the async highlighter a beat, then take the final frame
57
- await Bun.sleep(150);
58
- await setup.renderOnce();
59
- return setup.captureCharFrame();
60
- }
61
-
62
- describe("code blocks", () => {
63
- test("lines render verbatim: indentation preserved, no word-wrap", async () => {
64
- const f = await frame();
65
- expect(f).toContain(" const blocked = full < 0.6;");
66
- expect(f).toContain(' return <text>{blocked ? "blocked" : "clear"}</text>;');
67
- });
68
-
69
- test("the block carries its language tag", async () => {
70
- const f = await frame();
71
- expect(f).toContain("tsx");
72
- });
73
- });
74
-
75
- describe("block spacing", () => {
76
- test("a code block never glues to the list item above it", async () => {
77
- const lines = (await frame()).split("\n").map((l) => l.trimEnd());
78
- const li = lines.findIndex((l) => l.includes("wire the duck over MQTT"));
79
- const codeHeader = lines.findIndex((l) => l.trim().startsWith("tsx"));
80
- expect(li).toBeGreaterThan(-1);
81
- expect(codeHeader).toBeGreaterThan(li);
82
- expect(lines.slice(li + 1, codeHeader).some((l) => l === "")).toBe(true);
83
- });
84
-
85
- test("a heading never sits directly on the previous block", async () => {
86
- const lines = (await frame()).split("\n").map((l) => l.trimEnd());
87
- const gate = lines.findIndex((l) => l.trim() === "Gate");
88
- expect(gate).toBeGreaterThan(0);
89
- expect(lines[gate - 1]).toBe("");
90
- });
91
-
92
- test("consecutive list items stay tight", async () => {
93
- const listPlan = "# T\n\n- one\n- two\n";
94
- const s2 = server.core.sessionCreate({
95
- workspace: { repoRoot: "/repo", branch: "main" },
96
- artifact: { type: "plan", content: listPlan, meta: {} },
97
- });
98
- const setup = await testRender(<App home={home} sessionId={s2.id} />, { width: 120, height: 30 });
99
- const deadline = Date.now() + 10_000;
100
- while (Date.now() < deadline && !setup.captureCharFrame().includes("one")) {
101
- await Bun.sleep(25);
102
- await setup.renderOnce();
103
- }
104
- const lines = setup.captureCharFrame().split("\n").map((l) => l.trimEnd());
105
- const one = lines.findIndex((l) => l.includes("- one"));
106
- expect(lines[one + 1]).toContain("- two");
107
- });
108
- });
@@ -1,183 +0,0 @@
1
- /**
2
- * The rail submit confirm (tier 2): pressing submit expands the rail's
3
- * Submit button into a bordered confirm card - counts, verdict selector,
4
- * summary input, word-buttons. Char-frame assertions over the real App
5
- * and an in-process daemon, like App.test.tsx.
6
- */
7
-
8
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
9
- import { mkdtempSync, rmSync } from "node:fs";
10
- import { tmpdir } from "node:os";
11
- import { join } from "node:path";
12
- import React from "react";
13
- import { testRender } from "@opentui/react/test-utils";
14
- import { DaemonServer } from "@cueloop/daemon";
15
- import { makeAnchor, parseBlocks, type ReviewSession } from "@cueloop/schema";
16
- import { App } from "./App";
17
-
18
- const PLAN = `# Migration Plan
19
-
20
- ## Context
21
-
22
- The daemon persists sessions to disk atomically.
23
-
24
- ## Steps
25
-
26
- - move the store
27
- - add recovery
28
- `;
29
-
30
- let home: string;
31
- let server: DaemonServer;
32
- let session: ReviewSession;
33
-
34
- beforeEach(() => {
35
- home = mkdtempSync(join(tmpdir(), "cueloop-confirm-"));
36
- server = new DaemonServer({ home, idleExitMs: 0 });
37
- server.start();
38
- session = server.core.sessionCreate({
39
- workspace: { repoRoot: "/repo", branch: "main" },
40
- artifact: { type: "plan", content: PLAN, meta: { title: "Migration Plan", planPath: "plan.md" } },
41
- });
42
- });
43
- afterEach(() => {
44
- server.stop();
45
- rmSync(home, { recursive: true, force: true });
46
- });
47
-
48
- async function renderApp(options: { readOnly?: boolean } = {}) {
49
- const setup = await testRender(<App home={home} sessionId={session.id} readOnly={options.readOnly ?? false} />, {
50
- width: 120,
51
- height: 32,
52
- });
53
- for (let i = 0; i < 40 && !setup.captureCharFrame().includes("cueloop"); i++) {
54
- await Bun.sleep(25);
55
- await setup.renderOnce();
56
- }
57
- await setup.renderOnce();
58
- return setup;
59
- }
60
-
61
- type Setup = Awaited<ReturnType<typeof renderApp>>;
62
-
63
- async function press(setup: Setup, k: string): Promise<void> {
64
- if (k === "enter") setup.mockInput.pressKey("RETURN");
65
- else if (k === "escape") {
66
- setup.mockInput.pressKey("ESCAPE");
67
- // a bare ESC byte is an ambiguous sequence prefix; the parser settles late
68
- await Bun.sleep(120);
69
- } else if (k === "left") setup.mockInput.pressKey("ARROW_LEFT");
70
- else if (k === "right") setup.mockInput.pressKey("ARROW_RIGHT");
71
- else await setup.mockInput.typeText(k);
72
- await Bun.sleep(15);
73
- await setup.renderOnce();
74
- }
75
-
76
- /** Seed annotations directly through the daemon core (all on one block). */
77
- function seedAnnotations(count: number): void {
78
- const blocks = parseBlocks(PLAN);
79
- for (let index = 1; index <= count; index++) {
80
- server.core.sessionAnnotate(session.id, {
81
- id: `a_confirm_${index}`,
82
- kind: "comment",
83
- anchor: makeAnchor(blocks, 2, 0, 10),
84
- body: `note ${String(index).padStart(2, "0")}`,
85
- });
86
- }
87
- }
88
-
89
- describe("rail submit confirm", () => {
90
- test("submit expands the Submit button into the confirm card", async () => {
91
- const setup = await renderApp();
92
- expect(setup.captureCharFrame()).toContain("Submit review (0)");
93
- await press(setup, "enter");
94
- const frame = setup.captureCharFrame();
95
- // the card: title, honest counts, verdict selector, word-buttons
96
- expect(frame).toContain("submit review");
97
- expect(frame).toContain("0 annotations · 0 blocking");
98
- expect(frame).toContain("[Approve]"); // nothing pending: approve default
99
- expect(frame).toContain(" Submit ");
100
- expect(frame).toContain(" Cancel ");
101
- // key hints live in the status line, not on the buttons
102
- expect(frame).toContain("verdict ←/→ · ⏎ submit · esc cancel");
103
- // the bottom bar stayed a one-line hint: no detached verdict bar
104
- expect(frame).not.toContain("Submit review (0) on ⏎");
105
- });
106
-
107
- test("left/right cycles the verdict selector in the card", async () => {
108
- const setup = await renderApp();
109
- await press(setup, "enter");
110
- expect(setup.captureCharFrame()).toContain("[Approve]");
111
- await press(setup, "right");
112
- expect(setup.captureCharFrame()).toContain("[Changes]");
113
- await press(setup, "right");
114
- expect(setup.captureCharFrame()).toContain("[Comment]");
115
- await press(setup, "left");
116
- expect(setup.captureCharFrame()).toContain("[Changes]");
117
- });
118
-
119
- test("esc cancels the card and restores the plain Submit button", async () => {
120
- const setup = await renderApp();
121
- await press(setup, "enter");
122
- expect(setup.captureCharFrame()).toContain("[Approve]");
123
- await press(setup, "escape");
124
- const frame = setup.captureCharFrame();
125
- expect(frame).not.toContain("[Approve]");
126
- expect(frame).not.toContain("0 annotations");
127
- expect(frame).toContain("Submit review (0)");
128
- });
129
-
130
- test("enter in the card resolves the session through the controller", async () => {
131
- seedAnnotations(1);
132
- const setup = await renderApp();
133
- await press(setup, "enter");
134
- const frame = setup.captureCharFrame();
135
- expect(frame).toContain("1 annotations · 0 blocking");
136
- expect(frame).toContain("[Changes]"); // pending items: request changes default
137
- await setup.mockInput.typeText("Tighten the steps.");
138
- await press(setup, "enter");
139
- await Bun.sleep(120);
140
- await setup.renderOnce();
141
- const stored = server.core.sessionGet(session.id);
142
- expect(stored.status).toBe("resolved");
143
- expect(stored.verdict!.kind).toBe("request_changes");
144
- // the completion flow after submit is unchanged
145
- expect(setup.captureCharFrame()).toContain("✎ feedback sent");
146
- });
147
-
148
- test("the annotation stack stays scrollable while the card is open", async () => {
149
- seedAnnotations(12);
150
- const setup = await renderApp();
151
- // walk annotation focus to the last card: the rail scrolls it into view
152
- for (let index = 0; index < 12; index++) await press(setup, "n");
153
- const scrolled = setup.captureCharFrame();
154
- expect(scrolled).toContain("note 12");
155
- expect(scrolled).not.toContain("note 01");
156
- await press(setup, "enter");
157
- const opened = setup.captureCharFrame();
158
- // the card is pinned outside the scrollbox; the stack above stays
159
- // scrolled away from the top when the card takes its space
160
- expect(opened).toContain("12 annotations · 0 blocking");
161
- expect(opened).not.toContain("note 01");
162
- // the stack still scrolls with the card open: wheel over the rail
163
- // brings the last card back into view while the confirm card stays put
164
- for (let turn = 0; turn < 12; turn++) await setup.mockMouse.scroll(100, 10, "down");
165
- await setup.renderOnce();
166
- const scrolledWithCard = setup.captureCharFrame();
167
- expect(scrolledWithCard).toContain("note 12");
168
- expect(scrolledWithCard).toContain("12 annotations · 0 blocking");
169
- expect(scrolledWithCard).not.toContain("note 01");
170
- });
171
-
172
- test("read-only observers never see the confirm card", async () => {
173
- seedAnnotations(1);
174
- const setup = await renderApp({ readOnly: true });
175
- await press(setup, "enter");
176
- const frame = setup.captureCharFrame();
177
- expect(frame).toContain("observer - read-only");
178
- expect(frame).not.toContain("1 annotations");
179
- expect(frame).not.toContain("[Changes]");
180
- expect(setup.captureCharFrame()).not.toContain("verdict ←/→");
181
- expect(server.core.sessionGet(session.id).status).toBe("pending");
182
- });
183
- });
package/src/App.test.tsx DELETED
@@ -1,218 +0,0 @@
1
- /**
2
- * Virtual-terminal component tests (tier 2): the real App over a real
3
- * in-process daemon in a temp home. Char-frame assertions + mock keys -
4
- * the whole review loop drivable without a terminal.
5
- */
6
-
7
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
8
- import { mkdtempSync, rmSync } from "node:fs";
9
- import { tmpdir } from "node:os";
10
- import { join } from "node:path";
11
- import React from "react";
12
- import { testRender } from "@opentui/react/test-utils";
13
- import { DaemonServer } from "@cueloop/daemon";
14
- import type { ReviewSession } from "@cueloop/schema";
15
- import { App } from "./App";
16
-
17
- const PLAN = `# Migration Plan
18
-
19
- ## Context
20
-
21
- The daemon persists sessions to disk atomically.
22
-
23
- ## Steps
24
-
25
- - move the store
26
- - add recovery
27
- `;
28
-
29
- let home: string;
30
- let server: DaemonServer;
31
- let session: ReviewSession;
32
-
33
- beforeEach(() => {
34
- home = mkdtempSync(join(tmpdir(), "cueloop-app-"));
35
- server = new DaemonServer({ home, idleExitMs: 0 });
36
- server.start();
37
- session = server.core.sessionCreate({
38
- workspace: { repoRoot: "/repo", branch: "main" },
39
- artifact: { type: "plan", content: PLAN, meta: { title: "Migration Plan", planPath: "plan.md" } },
40
- });
41
- });
42
- afterEach(() => {
43
- server.stop();
44
- rmSync(home, { recursive: true, force: true });
45
- });
46
-
47
- async function renderApp(sessionId?: string) {
48
- const setup = await testRender(<App home={home} sessionId={sessionId ?? session.id} />, {
49
- width: 120,
50
- height: 32,
51
- });
52
- // let the async daemon connect + first fetch land
53
- for (let i = 0; i < 40 && !setup.captureCharFrame().includes("cueloop"); i++) {
54
- await Bun.sleep(25);
55
- await setup.renderOnce();
56
- }
57
- await setup.renderOnce();
58
- return setup;
59
- }
60
-
61
- type Setup = Awaited<ReturnType<typeof renderApp>>;
62
-
63
- /** Drive one key press: letters type as text; named keys use KeyCodes ids. */
64
- async function press(setup: Setup, k: string): Promise<void> {
65
- if (k === "enter") setup.mockInput.pressKey("RETURN");
66
- else if (k === "escape") setup.mockInput.pressKey("ESCAPE");
67
- else if (k === "backspace") setup.mockInput.pressKey("BACKSPACE");
68
- else if (k === "left") setup.mockInput.pressKey("ARROW_LEFT");
69
- else if (k === "right") setup.mockInput.pressKey("ARROW_RIGHT");
70
- else await type(setup, k);
71
- await Bun.sleep(15);
72
- await setup.renderOnce();
73
- }
74
-
75
- async function type(setup: Setup, text: string): Promise<void> {
76
- await setup.mockInput.typeText(text);
77
- await Bun.sleep(15);
78
- await setup.renderOnce();
79
- }
80
-
81
- describe("plan rendering", () => {
82
- test("renders the plan with headings, list markers, and the rail", async () => {
83
- const setup = await renderApp();
84
- const frame = setup.captureCharFrame();
85
- expect(frame).toContain("Migration Plan");
86
- expect(frame).toContain("Context");
87
- expect(frame).toContain("persists sessions to disk atomically");
88
- expect(frame).toContain("- move the store");
89
- expect(frame).toContain("Review (0)");
90
- expect(frame).toContain("Submit review (0)");
91
- });
92
- });
93
-
94
- describe("keyboard grammar", () => {
95
- test("j/k moves the cursor glyph between blocks", async () => {
96
- const setup = await renderApp();
97
- await press(setup, "j");
98
- await press(setup, "j");
99
- await setup.renderOnce();
100
- const lines = setup.captureCharFrame().split("\n");
101
- const cursorLine = lines.find((l) => l.includes("▎"))!;
102
- expect(cursorLine).toContain("persists sessions");
103
- });
104
-
105
- test("comment flow: c types a body, ⏎ saves to the daemon", async () => {
106
- const setup = await renderApp();
107
- await press(setup, "j");
108
- await press(setup, "j");
109
- await press(setup, "c");
110
- await setup.renderOnce();
111
- expect(setup.captureCharFrame()).toContain('comment on "The daemon');
112
- await type(setup, "Define atomically.");
113
- await press(setup, "enter");
114
- await Bun.sleep(80);
115
- await setup.renderOnce();
116
- const stored = server.core.sessionGet(session.id);
117
- expect(stored.annotations.length).toBe(1);
118
- expect(stored.annotations[0]!.body).toBe("Define atomically.");
119
- expect(setup.captureCharFrame()).toContain("COMMENT");
120
- });
121
-
122
- test("span mode: v + l selects words, c anchors the exact span", async () => {
123
- const setup = await renderApp();
124
- await press(setup, "j");
125
- await press(setup, "j");
126
- await press(setup, "v");
127
- await press(setup, "l"); // "The daemon"
128
- await press(setup, "c");
129
- await type(setup, "Which daemon?");
130
- await press(setup, "enter");
131
- await Bun.sleep(80);
132
- const stored = server.core.sessionGet(session.id);
133
- expect(stored.annotations[0]!.anchor.quote).toBe("The daemon");
134
- });
135
-
136
- test("x cuts a block into the working copy; x restores it", async () => {
137
- const setup = await renderApp();
138
- // move to "- move the store" (h1, h2, p, h2 = 4 steps in)
139
- for (let i = 0; i < 4; i++) await press(setup, "j");
140
- await press(setup, "x");
141
- await Bun.sleep(80);
142
- await setup.renderOnce();
143
- expect(server.core.sessionGet(session.id).workingCopy).not.toContain("move the store");
144
- expect(setup.captureCharFrame()).toContain("[cut]");
145
- await press(setup, "x");
146
- await Bun.sleep(80);
147
- await setup.renderOnce();
148
- expect(server.core.sessionGet(session.id).workingCopy).toBeUndefined();
149
- });
150
-
151
- test("e runs $EDITOR on the working copy and tracks the diff", async () => {
152
- const script = join(home, "fake-editor.sh");
153
- await Bun.write(script, `#!/bin/sh\nsed -i '' 's/atomically/very atomically/' "$1"\n`);
154
- Bun.spawnSync(["chmod", "+x", script]);
155
- process.env.CUELOOP_EDITOR = script;
156
- try {
157
- const setup = await renderApp();
158
- await press(setup, "e");
159
- await Bun.sleep(150);
160
- await setup.renderOnce();
161
- expect(server.core.sessionGet(session.id).workingCopy).toContain("very atomically");
162
- expect(setup.captureCharFrame()).toContain("[edited]");
163
- } finally {
164
- delete process.env.CUELOOP_EDITOR;
165
- }
166
- });
167
- });
168
-
169
- describe("submit", () => {
170
- test("⏎ opens the rail confirm card; verdict + summary resolve the session", async () => {
171
- const setup = await renderApp();
172
- await press(setup, "j");
173
- await press(setup, "j");
174
- await press(setup, "c");
175
- await type(setup, "Needs a phase list.");
176
- await press(setup, "enter");
177
- await Bun.sleep(80);
178
- await press(setup, "enter"); // open submit
179
- await setup.renderOnce();
180
- expect(setup.captureCharFrame()).toContain("[Changes]");
181
- await type(setup, "Expand the steps.");
182
- await press(setup, "enter");
183
- await Bun.sleep(120);
184
- await setup.renderOnce();
185
- const stored = server.core.sessionGet(session.id);
186
- expect(stored.status).toBe("resolved");
187
- expect(stored.verdict!.kind).toBe("request_changes");
188
- expect(stored.verdict!.feedback).toContain("Needs a phase list.");
189
- // submit hands the reviewer back to the agent via the completion overlay
190
- expect(setup.captureCharFrame()).toContain("✎ feedback sent");
191
- });
192
-
193
- test("approve via ←/→ verdict cycling", async () => {
194
- const setup = await renderApp();
195
- await press(setup, "enter");
196
- await setup.renderOnce();
197
- expect(setup.captureCharFrame()).toContain("[Approve]"); // no pending items → approve default
198
- await press(setup, "enter");
199
- await Bun.sleep(120);
200
- expect(server.core.sessionGet(session.id).verdict!.kind).toBe("approve");
201
- });
202
- });
203
-
204
- describe("inbox", () => {
205
- test("inbox mode renders and opens a session", async () => {
206
- const setup = await testRender(<App home={home} />, { width: 120, height: 32 });
207
- for (let i = 0; i < 40 && !setup.captureCharFrame().includes("inbox"); i++) {
208
- await Bun.sleep(25);
209
- await setup.renderOnce();
210
- }
211
- expect(setup.captureCharFrame()).toContain("inbox (1 pending)");
212
- expect(setup.captureCharFrame()).toContain("Migration Plan");
213
- await press(setup, "enter");
214
- await Bun.sleep(80);
215
- await setup.renderOnce();
216
- expect(setup.captureCharFrame()).toContain("Submit review (0)");
217
- });
218
- });