@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,177 @@
1
+ /** The share shortcut opens the two-column share dialog reachable from any view. */
2
+
3
+ import { afterEach, beforeEach, describe, expect, mock, 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 { Thread } from "@cueloop/schema";
11
+ import { App } from "./App";
12
+ import type { ShareTransport } from "../thread/thread-controller";
13
+ import {
14
+ isolateUserConfig,
15
+ press,
16
+ pressKey,
17
+ settle,
18
+ waitForText,
19
+ waitForTextGone,
20
+ } from "../testing/test-support";
21
+
22
+ const shareTransport: ShareTransport = {
23
+ publish: mock(async () => ({ line: "ssh p_share01@cueloop.dev", copied: true })),
24
+ pull: mock(async () => {
25
+ throw new Error("Unexpected share pull");
26
+ }),
27
+ push: mock(async () => {}),
28
+ watch: () => () => {},
29
+ revoke: async () => {},
30
+ parseShareId: (line) => line.match(/^ssh (\S+)@/)?.[1],
31
+ formatShareLine: (id: string) => "ssh " + id + "@cueloop.dev",
32
+ collaboratorAnnotations: () => [],
33
+ mergeFromShare: () => ({ annotations: [] }),
34
+ };
35
+
36
+ const PLAN = `# Migration Plan\n\n## Context\n\nThe daemon persists sessions to disk atomically.\n`;
37
+
38
+ let home: string;
39
+ let restoreUserConfig: () => void;
40
+ let server: DaemonServer;
41
+ let session: Thread;
42
+
43
+ beforeEach(() => {
44
+ home = mkdtempSync(join(tmpdir(), "cueloop-share-choice-"));
45
+ restoreUserConfig = isolateUserConfig(home);
46
+ server = new DaemonServer({ home, idleExitMs: 0 });
47
+ server.start();
48
+ session = server.core.sessionCreate({
49
+ workspace: { repoRoot: "/repo", branch: "main" },
50
+ artifact: {
51
+ type: "plan",
52
+ content: PLAN,
53
+ meta: { title: "Migration Plan", planPath: "plan.md" },
54
+ },
55
+ });
56
+ });
57
+ afterEach(() => {
58
+ restoreUserConfig();
59
+ server.stop();
60
+ rmSync(home, { recursive: true, force: true });
61
+ });
62
+
63
+ describe("share dialog", () => {
64
+ test("the shortcut opens the two-column share dialog", async () => {
65
+ // Arrange
66
+ const setup = await testRender(
67
+ <App home={home} sessionId={session.id} shareTransport={shareTransport} />,
68
+ { width: 120, height: 32 },
69
+ );
70
+
71
+ await waitForText(setup, "cueloop");
72
+
73
+ // Act
74
+ await pressKey(setup, "s", { ctrl: true });
75
+
76
+ // Assert - the nav categories and the empty-state new-link affordance
77
+ await waitForText(setup, "share externally");
78
+ await waitForText(setup, "export");
79
+ await waitForText(setup, "+ new link");
80
+ });
81
+
82
+ test("new link opens the wizard, and require auth reveals the allowlist", async () => {
83
+ // Arrange
84
+ const setup = await testRender(
85
+ <App home={home} sessionId={session.id} shareTransport={shareTransport} />,
86
+ { width: 120, height: 32 },
87
+ );
88
+
89
+ await waitForText(setup, "cueloop");
90
+
91
+ // Act: open the dialog, step into the body, activate "+ new link"
92
+ await pressKey(setup, "s", { ctrl: true });
93
+ await waitForText(setup, "+ new link");
94
+ await press(setup, "enter");
95
+ await press(setup, "enter");
96
+
97
+ // Assert - the wizard's fields
98
+ await waitForText(setup, "link name");
99
+ await waitForText(setup, "require auth");
100
+
101
+ // Act: focus the auth row and switch it on (enter toggles the focused row)
102
+ await press(setup, "down");
103
+ await press(setup, "enter");
104
+
105
+ // Assert - the handles input appears with its grayed placeholder
106
+ await waitForText(setup, "handle");
107
+
108
+ // Act: backspace on the empty allowlist collapses require auth back off
109
+ await press(setup, "backspace");
110
+
111
+ // Assert - the handles section is gone
112
+ await waitForTextGone(setup, "handle");
113
+ });
114
+
115
+ test("a private link with no handles cannot be created", async () => {
116
+ // Arrange
117
+ const setup = await testRender(
118
+ <App home={home} sessionId={session.id} shareTransport={shareTransport} />,
119
+ { width: 120, height: 32 },
120
+ );
121
+
122
+ await waitForText(setup, "cueloop");
123
+
124
+ // Act: open the wizard, turn auth on but add no handles, then try to create
125
+ await pressKey(setup, "s", { ctrl: true });
126
+ await waitForText(setup, "+ new link");
127
+ await press(setup, "enter"); // into the body
128
+ await press(setup, "enter"); // open the wizard
129
+ await waitForText(setup, "require auth");
130
+ await press(setup, "down"); // focus the auth row
131
+ await press(setup, "enter"); // toggle auth on (focus moves to the empty allowlist)
132
+ await press(setup, "down"); // move to the create action
133
+ await press(setup, "enter"); // create is blocked while the allowlist is empty
134
+ await settle(setup);
135
+
136
+ // Assert - the wizard is still open (nothing was created and the dialog did not fall back to the list)
137
+ expect(setup.captureCharFrame()).toContain("require auth");
138
+ });
139
+
140
+ test("right enters the links body and left returns to the nav", async () => {
141
+ // Arrange
142
+ const setup = await testRender(
143
+ <App home={home} sessionId={session.id} shareTransport={shareTransport} />,
144
+ { width: 120, height: 32 },
145
+ );
146
+
147
+ await waitForText(setup, "cueloop");
148
+
149
+ // Act + Assert: right arrow crosses into the body (its hint appears)
150
+ await pressKey(setup, "s", { ctrl: true });
151
+ await waitForText(setup, "+ new link");
152
+ await press(setup, "right");
153
+ await waitForText(setup, "h back");
154
+
155
+ // Act + Assert: left arrow returns to the nav (its hint returns)
156
+ await press(setup, "left");
157
+ await waitForText(setup, "l/enter open");
158
+ });
159
+
160
+ test("escape closes the dialog", async () => {
161
+ // Arrange
162
+ const setup = await testRender(
163
+ <App home={home} sessionId={session.id} shareTransport={shareTransport} />,
164
+ { width: 120, height: 32 },
165
+ );
166
+
167
+ await waitForText(setup, "cueloop");
168
+
169
+ // Act
170
+ await pressKey(setup, "s", { ctrl: true });
171
+ await waitForText(setup, "share externally");
172
+ await press(setup, "escape");
173
+
174
+ // Assert
175
+ await waitForTextGone(setup, "share externally");
176
+ });
177
+ });
@@ -0,0 +1,267 @@
1
+ /** The send-message confirm: cmd+enter opens a centered overlay with a message selector, summary input, and word-buttons. Char-frame assertions over the real App and an in-process daemon, like App.test.tsx. */
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 { makeAnchor, parseBlocks, type Thread } from "@cueloop/schema";
11
+ import { App } from "./App";
12
+ import {
13
+ isolateUserConfig,
14
+ press,
15
+ pressKey,
16
+ settle,
17
+ waitForText,
18
+ waitForTextGone,
19
+ } from "../testing/test-support";
20
+
21
+ const PLAN = `# Migration Plan
22
+
23
+ ## Context
24
+
25
+ The daemon persists sessions to disk atomically.
26
+
27
+ ## Steps
28
+
29
+ - move the store
30
+ - add recovery
31
+ `;
32
+
33
+ let home: string;
34
+ let restoreUserConfig: () => void;
35
+ let server: DaemonServer;
36
+ let session: Thread;
37
+
38
+ beforeEach(() => {
39
+ home = mkdtempSync(join(tmpdir(), "cueloop-confirm-"));
40
+ restoreUserConfig = isolateUserConfig(home);
41
+ server = new DaemonServer({ home, idleExitMs: 0 });
42
+ server.start();
43
+ session = server.core.sessionCreate({
44
+ workspace: { repoRoot: "/repo", branch: "main" },
45
+ artifact: {
46
+ type: "plan",
47
+ content: PLAN,
48
+ meta: { title: "Migration Plan", planPath: "plan.md" },
49
+ },
50
+ });
51
+ });
52
+ afterEach(() => {
53
+ restoreUserConfig();
54
+ server.stop();
55
+ rmSync(home, { recursive: true, force: true });
56
+ });
57
+
58
+ async function renderApp(options: { readOnly?: boolean } = {}) {
59
+ const setup = await testRender(
60
+ <App home={home} sessionId={session.id} readOnly={options.readOnly ?? false} />,
61
+ {
62
+ width: 120,
63
+ height: 32,
64
+ },
65
+ );
66
+
67
+ await waitForText(setup, "cueloop");
68
+
69
+ return setup;
70
+ }
71
+
72
+ /** Seed annotations directly through the daemon core (all on one block). */
73
+ function seedAnnotations(count: number): void {
74
+ const blocks = parseBlocks(PLAN);
75
+
76
+ for (let index = 1; index <= count; index++) {
77
+ server.core.sessionAnnotate(session.id, {
78
+ id: `a_confirm_${index}`,
79
+ kind: "comment",
80
+ anchor: makeAnchor(blocks, 2, 0, 10),
81
+ body: `note ${String(index).padStart(2, "0")}`,
82
+ });
83
+ }
84
+ }
85
+
86
+ describe("send message confirm", () => {
87
+ test("cmd+enter opens the send-message confirm overlay with stable message choices", async () => {
88
+ // Arrange
89
+ const setup = await renderApp();
90
+
91
+ // Assert - the footer carries the send control before the overlay opens
92
+ expect(setup.captureCharFrame()).toContain("Send message (0)");
93
+
94
+ // Act
95
+ await pressKey(setup, "RETURN", { meta: true });
96
+
97
+ // Assert - the overlay: message selector and word-buttons
98
+ await waitForText(setup, "[approve]"); // nothing pending: approve default
99
+ const initialFrame = setup.captureCharFrame();
100
+ const initialSelector = initialFrame
101
+ .split("\n")
102
+ .find((row) => row.includes("[comment] [approve] [changes]"));
103
+
104
+ expect(initialSelector).toBeDefined();
105
+ expect(initialFrame).toContain(" send ");
106
+ expect(initialFrame).toContain(" cancel ");
107
+
108
+ // Act - move the active choice across the selector
109
+ await press(setup, "right");
110
+ const changesSelector = setup
111
+ .captureCharFrame()
112
+ .split("\n")
113
+ .find((row) => row.includes("[comment] [approve] [changes]"));
114
+
115
+ await press(setup, "right");
116
+ const commentSelector = setup
117
+ .captureCharFrame()
118
+ .split("\n")
119
+ .find((row) => row.includes("[comment] [approve] [changes]"));
120
+
121
+ // Assert - active styling does not move or resize any choice
122
+ expect(changesSelector).toBe(initialSelector);
123
+ expect(commentSelector).toBe(initialSelector);
124
+ });
125
+
126
+ test("left/right cycles the message selector in the overlay", async () => {
127
+ // Arrange
128
+ const setup = await renderApp();
129
+
130
+ await pressKey(setup, "RETURN", { meta: true });
131
+
132
+ // Assert
133
+ await waitForText(setup, "[approve]");
134
+
135
+ // Act
136
+ await press(setup, "right");
137
+
138
+ // Assert
139
+ await waitForText(setup, "[changes]");
140
+
141
+ // Act
142
+ await press(setup, "right");
143
+
144
+ // Assert
145
+ await waitForText(setup, "[comment]");
146
+
147
+ // Act
148
+ await press(setup, "left");
149
+
150
+ // Assert
151
+ await waitForText(setup, "[changes]");
152
+ });
153
+
154
+ test("esc closes the overlay", async () => {
155
+ // Arrange
156
+ const setup = await renderApp();
157
+
158
+ await pressKey(setup, "RETURN", { meta: true });
159
+
160
+ // Assert
161
+ await waitForText(setup, "[approve]");
162
+
163
+ // Act
164
+ await press(setup, "escape");
165
+
166
+ // Assert - a bare ESC settles after the parser's escape-sequence window
167
+ const frame = await waitForTextGone(setup, "[approve]");
168
+
169
+ expect(frame).not.toContain(" cancel ");
170
+ });
171
+
172
+ test("enter in the overlay resolves the session through the controller", async () => {
173
+ // Arrange
174
+ seedAnnotations(1);
175
+ const setup = await renderApp();
176
+
177
+ // Act
178
+ await pressKey(setup, "RETURN", { meta: true });
179
+
180
+ // Assert - opens on the default message, then cycle to request changes
181
+ await waitForText(setup, "[approve]");
182
+ await press(setup, "right");
183
+ await waitForText(setup, "[changes]");
184
+
185
+ // Act
186
+ await setup.mockInput.typeText("Tighten the steps.");
187
+ await pressKey(setup, "RETURN", { meta: true });
188
+
189
+ // Assert - the completion flow after submit is unchanged
190
+ await waitForText(setup, "feedback sent");
191
+ const stored = server.core.sessionGet(session.id);
192
+
193
+ expect(stored.status).toBe("resolved");
194
+ expect(stored.message!.outcome).toBe("changes_requested");
195
+ });
196
+
197
+ test("Comment sends feedback while leaving the Thread open", async () => {
198
+ seedAnnotations(1);
199
+ const setup = await renderApp();
200
+
201
+ await pressKey(setup, "RETURN", { meta: true });
202
+ await waitForText(setup, "[approve]");
203
+ await press(setup, "left");
204
+ await waitForText(setup, "[comment]");
205
+
206
+ await setup.mockInput.typeText("For now.");
207
+ await pressKey(setup, "RETURN", { meta: true });
208
+
209
+ await waitForText(setup, "comment sent - thread stays open");
210
+ const stored = server.core.sessionGet(session.id);
211
+
212
+ expect(stored.status).toBe("pending");
213
+ expect(stored.message!.outcome).toBe("comment");
214
+ expect(stored.message!.annotations?.map((annotation) => annotation.id)).toEqual([
215
+ "a_confirm_1",
216
+ ]);
217
+ expect(setup.captureCharFrame()).not.toContain("feedback sent");
218
+ });
219
+
220
+ test("typing / in the summary opens the skills/actions palette", async () => {
221
+ // Arrange
222
+ seedAnnotations(1);
223
+ const setup = await renderApp();
224
+
225
+ await pressKey(setup, "RETURN", { meta: true });
226
+ await waitForText(setup, "[approve]");
227
+
228
+ // Act
229
+ await setup.mockInput.typeText("/restate");
230
+
231
+ // Assert - the same slash palette the inline composer shows
232
+ await waitForText(setup, "restate-simplified");
233
+ });
234
+
235
+ test("pasting an image into the summary drops in an [Image #n] placeholder", async () => {
236
+ // Arrange
237
+ const setup = await renderApp();
238
+
239
+ await pressKey(setup, "RETURN", { meta: true });
240
+ await waitForText(setup, "[approve]");
241
+
242
+ // Act - a burst of control bytes stands in for the binary an image paste delivers
243
+ await setup.mockInput.typeText("look at this ");
244
+ await setup.mockInput.pasteBracketedText("");
245
+
246
+ // Assert - the raw bytes never reach the draft; a numbered placeholder does
247
+ await waitForText(setup, "[Image #1]");
248
+ expect(setup.captureCharFrame()).toContain("look at this [Image #1]");
249
+ });
250
+
251
+ test("read-only observers cannot open the confirm overlay", async () => {
252
+ // Arrange
253
+ seedAnnotations(1);
254
+ const setup = await renderApp({ readOnly: true });
255
+
256
+ // Act
257
+ await pressKey(setup, "RETURN", { meta: true });
258
+ await settle(setup);
259
+
260
+ // Assert - no overlay, and the session stays pending
261
+ const frame = setup.captureCharFrame();
262
+
263
+ expect(frame).not.toContain("[approve]");
264
+ expect(frame).not.toContain(" cancel ");
265
+ expect(server.core.sessionGet(session.id).status).toBe("pending");
266
+ });
267
+ });