@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
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Typed settings rows for the settings dialog: toggle (on/off), cycle
3
+ * (enumerated options), and text (freeform value). Every row is one line -
4
+ * label column left, value right - and presses go through `onPress` so mouse
5
+ * and keyboard activation share one path.
6
+ */
7
+
8
+ import React from "react";
9
+ import type { Theme } from "../../appearance/theme";
10
+ import { useComponentTheme } from "../../appearance/components/theme-context";
11
+
12
+ export interface SettingsRowDescriptor {
13
+ key: string;
14
+ label: string;
15
+ kind: "toggle" | "cycle" | "text";
16
+ options?: string[];
17
+ hint?: string;
18
+ }
19
+
20
+ interface RowShellProps {
21
+ label: string;
22
+ isActive: boolean;
23
+ onPress: () => void;
24
+ theme?: Theme;
25
+ children: React.ReactNode;
26
+ }
27
+
28
+ function RowShell({ label, isActive, onPress, theme, children }: RowShellProps): React.ReactNode {
29
+ const tokens = useComponentTheme(theme);
30
+
31
+ return (
32
+ <box
33
+ style={{
34
+ flexDirection: "row",
35
+ height: 1,
36
+ backgroundColor: isActive ? tokens.border : undefined,
37
+ }}
38
+ onMouseUp={onPress}
39
+ >
40
+ <box style={{ width: 26, paddingLeft: 1 }}>
41
+ <text fg={isActive ? tokens.text : tokens.textMuted}>{label}</text>
42
+ </box>
43
+ {children}
44
+ </box>
45
+ );
46
+ }
47
+
48
+ export interface ToggleRowProps {
49
+ label: string;
50
+ value: boolean;
51
+ isActive: boolean;
52
+ onPress: () => void;
53
+ theme?: Theme;
54
+ }
55
+
56
+ export function ToggleRow({
57
+ label,
58
+ value,
59
+ isActive,
60
+ onPress,
61
+ theme,
62
+ }: ToggleRowProps): React.ReactNode {
63
+ const tokens = useComponentTheme(theme);
64
+
65
+ return (
66
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
67
+ <text fg={value ? tokens.green : tokens.accent}>{value ? "on" : "off"}</text>
68
+ </RowShell>
69
+ );
70
+ }
71
+
72
+ export interface CycleRowProps {
73
+ label: string;
74
+ value: string;
75
+ isActive: boolean;
76
+ onPress: () => void;
77
+ theme?: Theme;
78
+ }
79
+
80
+ export function CycleRow({
81
+ label,
82
+ value,
83
+ isActive,
84
+ onPress,
85
+ theme,
86
+ }: CycleRowProps): React.ReactNode {
87
+ const tokens = useComponentTheme(theme);
88
+
89
+ return (
90
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
91
+ <text fg={tokens.accent}>{value}</text>
92
+ </RowShell>
93
+ );
94
+ }
95
+
96
+ export interface TextRowProps {
97
+ label: string;
98
+ value: string;
99
+ isActive: boolean;
100
+ onPress: () => void;
101
+ theme?: Theme;
102
+ }
103
+
104
+ export function TextRow({ label, value, isActive, onPress, theme }: TextRowProps): React.ReactNode {
105
+ const tokens = useComponentTheme(theme);
106
+
107
+ return (
108
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
109
+ <text fg={tokens.accent}>{value}</text>
110
+ </RowShell>
111
+ );
112
+ }
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import { DEFAULT_QUICK_ACTIONS } from "../config";
4
+ import type { Story, StoryMeta } from "../../stories/story";
5
+ import { QuickActionsEditor } from "./quick-actions-editor";
6
+
7
+ export const meta: StoryMeta = { title: "Chrome/QuickActionsEditor" };
8
+
9
+ /** The editor renders inside the settings body column. */
10
+ function BodyFrame({ children }: { children: React.ReactNode }): React.ReactNode {
11
+ return (
12
+ <box style={{ width: 54, height: 22, paddingLeft: 2, backgroundColor: DARK.elevated }}>
13
+ {children}
14
+ </box>
15
+ );
16
+ }
17
+
18
+ const noop = () => {};
19
+
20
+ export const Collapsed: Story = {
21
+ render: () => (
22
+ <BodyFrame>
23
+ <QuickActionsEditor
24
+ actions={DEFAULT_QUICK_ACTIONS}
25
+ selectedIndex={0}
26
+ expandedIndex={null}
27
+ expandedField="prompt"
28
+ onToggleExpand={noop}
29
+ onEditPrompt={() => {}}
30
+ onEditMetadata={noop}
31
+ onReset={noop}
32
+ onAdd={noop}
33
+ theme={DARK}
34
+ />
35
+ </BodyFrame>
36
+ ),
37
+ size: { width: 54, height: 22 },
38
+ };
39
+
40
+ export const RowExpanded: Story = {
41
+ render: () => (
42
+ <BodyFrame>
43
+ <QuickActionsEditor
44
+ actions={DEFAULT_QUICK_ACTIONS}
45
+ selectedIndex={1}
46
+ expandedIndex={1}
47
+ expandedField="prompt"
48
+ onToggleExpand={noop}
49
+ onEditPrompt={() => {}}
50
+ onEditMetadata={noop}
51
+ onReset={noop}
52
+ onAdd={noop}
53
+ theme={DARK}
54
+ />
55
+ </BodyFrame>
56
+ ),
57
+ size: { width: 54, height: 22 },
58
+ };
@@ -0,0 +1,120 @@
1
+ /** The Actions editor scrolls the list, and an expanded row edits both the title and the description. */
2
+
3
+ import { describe, expect, test } from "bun:test";
4
+ import React from "react";
5
+ import { testRender } from "@opentui/react/test-utils";
6
+ import type { QuickAction } from "../config";
7
+ import { locateText, settle, typeText } from "../../testing/test-support";
8
+ import { QuickActionsEditor } from "./quick-actions-editor";
9
+
10
+ const MANY: QuickAction[] = Array.from({ length: 14 }, (_, index) => ({
11
+ prompt: `Action number ${index}`,
12
+ }));
13
+
14
+ async function mount(selectedIndex: number) {
15
+ const setup = await testRender(
16
+ <QuickActionsEditor
17
+ actions={MANY}
18
+ selectedIndex={selectedIndex}
19
+ expandedIndex={null}
20
+ expandedField="prompt"
21
+ onToggleExpand={() => {}}
22
+ onEditPrompt={() => {}}
23
+ onEditMetadata={() => {}}
24
+ onReset={() => {}}
25
+ onAdd={() => {}}
26
+ />,
27
+ { width: 60, height: 10 },
28
+ );
29
+
30
+ await settle(setup);
31
+ await settle(setup);
32
+ await settle(setup);
33
+ await settle(setup);
34
+
35
+ return setup;
36
+ }
37
+
38
+ describe("QuickActionsEditor scrolling", () => {
39
+ test("a list too long for the pane clips into the scroll area instead of overlapping rows", async () => {
40
+ const setup = await mount(0);
41
+ const lines = setup.captureCharFrame().split("\n");
42
+
43
+ // the top rows read cleanly on their own lines - the overflow no longer paints rows on top of each other
44
+ expect(lines.some((line) => line.includes("Action number 0"))).toBe(true);
45
+ expect(lines.some((line) => line.includes("Action number 1"))).toBe(true);
46
+ // a bottom row is scrolled out of the clipped pane, not overlaid onto a visible one
47
+ expect(setup.captureCharFrame()).not.toContain("Action number 13");
48
+
49
+ setup.renderer.destroy();
50
+ });
51
+ });
52
+
53
+ describe("editing an expanded action", () => {
54
+ test("an expanded row edits both the title and the description", async () => {
55
+ const prompts: string[] = [];
56
+ const metadatas: string[] = [];
57
+ const setup = await testRender(
58
+ <QuickActionsEditor
59
+ actions={[{ prompt: "Ship it", metadata: "be terse" }]}
60
+ selectedIndex={0}
61
+ expandedIndex={0}
62
+ expandedField="prompt"
63
+ onToggleExpand={() => {}}
64
+ onEditPrompt={(_index, prompt) => prompts.push(prompt)}
65
+ onEditMetadata={(_index, metadata) => metadatas.push(metadata)}
66
+ onReset={() => {}}
67
+ onAdd={() => {}}
68
+ />,
69
+ { width: 60, height: 8 },
70
+ );
71
+
72
+ await settle(setup);
73
+ await settle(setup);
74
+
75
+ await typeText(setup, "!");
76
+ expect(prompts.at(-1)).toBe("Ship it!");
77
+
78
+ setup.mockInput.pressKey("RETURN");
79
+ await settle(setup);
80
+ await typeText(setup, "X");
81
+ expect(metadatas.at(-1)).toBe("be terseX");
82
+
83
+ setup.renderer.destroy();
84
+ });
85
+
86
+ test("clicking the description row opens the editor on the metadata field directly", async () => {
87
+ const toggles: Array<{ index: number; field: "prompt" | "metadata" }> = [];
88
+ const setup = await testRender(
89
+ <QuickActionsEditor
90
+ actions={[{ prompt: "Ship it", metadata: "be terse" }]}
91
+ selectedIndex={0}
92
+ expandedIndex={null}
93
+ expandedField="prompt"
94
+ onToggleExpand={(index, field) => toggles.push({ index, field })}
95
+ onEditPrompt={() => {}}
96
+ onEditMetadata={() => {}}
97
+ onReset={() => {}}
98
+ onAdd={() => {}}
99
+ />,
100
+ { width: 60, height: 8 },
101
+ );
102
+
103
+ await settle(setup);
104
+
105
+ // clicking the title opens the title field; clicking the description opens the metadata field
106
+ const title = locateText(setup, "Ship it");
107
+
108
+ await setup.mockMouse.click(title.column, title.row);
109
+ const description = locateText(setup, "be terse");
110
+
111
+ await setup.mockMouse.click(description.column, description.row);
112
+
113
+ expect(toggles).toEqual([
114
+ { index: 0, field: "prompt" },
115
+ { index: 0, field: "metadata" },
116
+ ]);
117
+
118
+ setup.renderer.destroy();
119
+ });
120
+ });
@@ -0,0 +1,222 @@
1
+ /**
2
+ * The Settings "Actions" body: the quick-action vocabulary editor. Each row is a
3
+ * clickable prompt; the selected-and-expanded row reveals a focused input for the
4
+ * per-action system prompt (the extra guidance appended when the action is used).
5
+ * A reset-to-defaults control and an add-action row bracket the list. Controlled:
6
+ * the app owns the actions, the selection, and which row is expanded.
7
+ */
8
+
9
+ import React, { useEffect, useRef, useState } from "react";
10
+ import type { KeyBinding, ScrollBoxRenderable, TextareaRenderable } from "@opentui/core";
11
+ import type { QuickAction } from "../config";
12
+ import type { Theme } from "../../appearance/theme";
13
+ import { ScrollArea } from "../../ui/components/ScrollArea";
14
+ import { useComponentTheme } from "../../appearance/components/theme-context";
15
+
16
+ // ⏎ never inserts a newline: a system prompt is one line, esc closes the editor.
17
+ const ACTION_INPUT_KEY_BINDINGS: KeyBinding[] = [{ name: "return", action: "submit" }];
18
+
19
+ export interface QuickActionsEditorProps {
20
+ actions: QuickAction[];
21
+ selectedIndex: number;
22
+ expandedIndex: number | null;
23
+ expandedField: "prompt" | "metadata";
24
+ onToggleExpand: (index: number, field: "prompt" | "metadata") => void;
25
+ onEditPrompt: (index: number, prompt: string) => void;
26
+ onEditMetadata: (index: number, metadata: string) => void;
27
+ onReset: () => void;
28
+ onAdd: () => void;
29
+ theme?: Theme;
30
+ }
31
+
32
+ export function QuickActionsEditor({
33
+ actions,
34
+ selectedIndex,
35
+ expandedIndex,
36
+ expandedField,
37
+ onToggleExpand,
38
+ onEditPrompt,
39
+ onEditMetadata,
40
+ onReset,
41
+ onAdd,
42
+ theme,
43
+ }: QuickActionsEditorProps): React.ReactNode {
44
+ const tokens = useComponentTheme(theme);
45
+ const scrollRef = useRef<ScrollBoxRenderable | null>(null);
46
+
47
+ useEffect(() => {
48
+ scrollRef.current?.scrollChildIntoView(`action-row-${selectedIndex}`);
49
+ }, [selectedIndex]);
50
+
51
+ return (
52
+ <box style={{ flexDirection: "column", flexGrow: 1 }}>
53
+ <box style={{ flexDirection: "row" }}>
54
+ <box style={{ flexGrow: 1 }} />
55
+ <box onMouseUp={onReset}>
56
+ <text fg={tokens.textDim}>reset</text>
57
+ </box>
58
+ </box>
59
+ <box style={{ height: 1 }} />
60
+ <ScrollArea scrollRef={scrollRef}>
61
+ {actions.map((action, index) => (
62
+ <ActionRow
63
+ key={index}
64
+ rowId={`action-row-${index}`}
65
+ action={action}
66
+ isSelected={index === selectedIndex}
67
+ isExpanded={index === expandedIndex}
68
+ initialField={expandedField}
69
+ onToggleExpand={(field) => onToggleExpand(index, field)}
70
+ onEditPrompt={(prompt) => onEditPrompt(index, prompt)}
71
+ onEditMetadata={(metadata) => onEditMetadata(index, metadata)}
72
+ theme={theme}
73
+ />
74
+ ))}
75
+ <box
76
+ id={`action-row-${actions.length}`}
77
+ style={{ backgroundColor: selectedIndex === actions.length ? tokens.border : undefined }}
78
+ onMouseUp={onAdd}
79
+ >
80
+ <text fg={tokens.textMuted}>+ Add action</text>
81
+ </box>
82
+ </ScrollArea>
83
+ </box>
84
+ );
85
+ }
86
+
87
+ function ActionRow({
88
+ rowId,
89
+ action,
90
+ isSelected,
91
+ isExpanded,
92
+ initialField,
93
+ onToggleExpand,
94
+ onEditPrompt,
95
+ onEditMetadata,
96
+ theme,
97
+ }: {
98
+ rowId: string;
99
+ action: QuickAction;
100
+ isSelected: boolean;
101
+ isExpanded: boolean;
102
+ initialField: "prompt" | "metadata";
103
+ onToggleExpand: (field: "prompt" | "metadata") => void;
104
+ onEditPrompt: (prompt: string) => void;
105
+ onEditMetadata: (metadata: string) => void;
106
+ theme?: Theme;
107
+ }): React.ReactNode {
108
+ const tokens = useComponentTheme(theme);
109
+
110
+ if (isExpanded) {
111
+ return (
112
+ <box id={rowId} style={{ flexDirection: "column" }}>
113
+ <ActionEditor
114
+ action={action}
115
+ isSelected={isSelected}
116
+ initialField={initialField}
117
+ onEditPrompt={onEditPrompt}
118
+ onEditMetadata={onEditMetadata}
119
+ onDone={() => onToggleExpand("prompt")}
120
+ tokens={tokens}
121
+ />
122
+ </box>
123
+ );
124
+ }
125
+
126
+ return (
127
+ <box id={rowId} style={{ flexDirection: "column" }}>
128
+ <box
129
+ style={{ backgroundColor: isSelected ? tokens.border : undefined }}
130
+ onMouseUp={() => onToggleExpand("prompt")}
131
+ >
132
+ <text fg={isSelected ? tokens.text : tokens.textMuted}>{action.prompt}</text>
133
+ </box>
134
+ {action.metadata ? (
135
+ <box onMouseUp={() => onToggleExpand("metadata")}>
136
+ <text fg={tokens.textDim}>{truncateMetadata(action.metadata)}</text>
137
+ </box>
138
+ ) : null}
139
+ </box>
140
+ );
141
+ }
142
+
143
+ /**
144
+ * The two-field editor for an expanded row: the action title over its system
145
+ * prompt. Mounts fresh per expand, so focus starts on the clicked field without a
146
+ * reset effect; ⏎ steps title -> description -> close, and a click focuses either.
147
+ */
148
+ function ActionEditor({
149
+ action,
150
+ isSelected,
151
+ initialField,
152
+ onEditPrompt,
153
+ onEditMetadata,
154
+ onDone,
155
+ tokens,
156
+ }: {
157
+ action: QuickAction;
158
+ isSelected: boolean;
159
+ initialField: "prompt" | "metadata";
160
+ onEditPrompt: (prompt: string) => void;
161
+ onEditMetadata: (metadata: string) => void;
162
+ onDone: () => void;
163
+ tokens: Theme;
164
+ }): React.ReactNode {
165
+ const promptRef = useRef<TextareaRenderable | null>(null);
166
+ const metadataRef = useRef<TextareaRenderable | null>(null);
167
+ const [activeField, setActiveField] = useState<"prompt" | "metadata">(initialField);
168
+
169
+ // the focused field types from its end, not from the caret parked at the start
170
+ useEffect(() => {
171
+ const editor = activeField === "prompt" ? promptRef.current : metadataRef.current;
172
+
173
+ if (editor) editor.cursorOffset = editor.plainText.length;
174
+ }, [activeField]);
175
+
176
+ const fieldStyle = {
177
+ height: 1,
178
+ flexGrow: 1,
179
+ backgroundColor: tokens.elevated,
180
+ focusedBackgroundColor: tokens.elevated,
181
+ textColor: tokens.text,
182
+ focusedTextColor: tokens.text,
183
+ } as const;
184
+
185
+ return (
186
+ <>
187
+ <box
188
+ style={{ flexDirection: "row", backgroundColor: isSelected ? tokens.border : undefined }}
189
+ >
190
+ <textarea
191
+ ref={promptRef}
192
+ focused={activeField === "prompt"}
193
+ initialValue={action.prompt}
194
+ placeholder="action title"
195
+ keyBindings={ACTION_INPUT_KEY_BINDINGS}
196
+ onMouseUp={() => setActiveField("prompt")}
197
+ onSubmit={() => setActiveField("metadata")}
198
+ onContentChange={() => onEditPrompt(promptRef.current?.plainText ?? "")}
199
+ style={fieldStyle}
200
+ />
201
+ </box>
202
+ <box style={{ flexDirection: "row" }}>
203
+ <textarea
204
+ ref={metadataRef}
205
+ focused={activeField === "metadata"}
206
+ initialValue={action.metadata ?? ""}
207
+ placeholder="extra system prompt appended to this comment (optional)"
208
+ keyBindings={ACTION_INPUT_KEY_BINDINGS}
209
+ onMouseUp={() => setActiveField("metadata")}
210
+ onSubmit={onDone}
211
+ onContentChange={() => onEditMetadata(metadataRef.current?.plainText ?? "")}
212
+ style={fieldStyle}
213
+ />
214
+ </box>
215
+ </>
216
+ );
217
+ }
218
+
219
+ /** A one-line preview of the system prompt under a collapsed row. */
220
+ function truncateMetadata(metadata: string): string {
221
+ return metadata.length > 44 ? `${metadata.slice(0, 43)}…` : metadata;
222
+ }
@@ -0,0 +1,78 @@
1
+ import { expect, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { loadConfig, persistIdentity } from "./config";
6
+
7
+ function withTempConfig(run: (path: string) => void): void {
8
+ const directory = mkdtempSync(join(tmpdir(), "cueloop-config-"));
9
+
10
+ try {
11
+ run(join(directory, "config.toml"));
12
+ } finally {
13
+ rmSync(directory, { recursive: true, force: true });
14
+ }
15
+ }
16
+
17
+ test("defaults identity to a typed provider with no name", () => {
18
+ withTempConfig((path) => {
19
+ expect(loadConfig({ userConfigPath: path }).identity).toEqual({ provider: "typed" });
20
+ });
21
+ });
22
+
23
+ test("persists and reloads a GitHub-synced identity", () => {
24
+ withTempConfig((path) => {
25
+ persistIdentity({ name: "Markus M", provider: "github" }, path);
26
+
27
+ expect(loadConfig({ userConfigPath: path }).identity).toEqual({
28
+ name: "Markus M",
29
+ provider: "github",
30
+ });
31
+ });
32
+ });
33
+
34
+ test("rewrites the identity block in place on a re-sync", () => {
35
+ withTempConfig((path) => {
36
+ persistIdentity({ name: "typed name", provider: "typed" }, path);
37
+ persistIdentity({ name: "markus", provider: "github" }, path);
38
+
39
+ expect(loadConfig({ userConfigPath: path }).identity).toEqual({
40
+ name: "markus",
41
+ provider: "github",
42
+ });
43
+ });
44
+ });
45
+
46
+ test("a display name containing a bracket survives a re-sync without corrupting the config", () => {
47
+ withTempConfig((path) => {
48
+ persistIdentity({ name: "Alice [Smith]", provider: "typed" }, path);
49
+ persistIdentity({ name: "Bob", provider: "github" }, path);
50
+
51
+ expect(loadConfig({ userConfigPath: path }).identity).toEqual({
52
+ name: "Bob",
53
+ provider: "github",
54
+ });
55
+ });
56
+ });
57
+
58
+ test("repository config cannot forge the reviewer identity", () => {
59
+ withTempConfig((userPath) => {
60
+ persistIdentity({ name: "Me", provider: "typed" }, userPath);
61
+ const repoRoot = mkdtempSync(join(tmpdir(), "cueloop-repo-"));
62
+
63
+ try {
64
+ mkdirSync(join(repoRoot, ".cueloop"), { recursive: true });
65
+ writeFileSync(
66
+ join(repoRoot, ".cueloop", "config.toml"),
67
+ '[identity]\nname = "Forged"\nprovider = "github"\n',
68
+ );
69
+
70
+ expect(loadConfig({ userConfigPath: userPath, repoRoot }).identity).toEqual({
71
+ name: "Me",
72
+ provider: "typed",
73
+ });
74
+ } finally {
75
+ rmSync(repoRoot, { recursive: true, force: true });
76
+ }
77
+ });
78
+ });