@cueloop/client 0.1.0-alpha.10 → 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 (355) 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 +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.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 +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 -88
  333. package/src/App.plan-review.test.tsx +0 -259
  334. package/src/App.readonly.test.tsx +0 -148
  335. package/src/App.rendering.test.tsx +0 -108
  336. package/src/App.submit-confirm.test.tsx +0 -183
  337. package/src/App.test.tsx +0 -218
  338. package/src/App.tsx +0 -1249
  339. package/src/config.test.ts +0 -116
  340. package/src/config.ts +0 -150
  341. package/src/editor.ts +0 -34
  342. package/src/herdr.test.ts +0 -68
  343. package/src/herdr.ts +0 -34
  344. package/src/keymap.test.ts +0 -270
  345. package/src/keymap.ts +0 -173
  346. package/src/run.ts +0 -30
  347. package/src/serve.ts +0 -75
  348. package/src/session-controller.ts +0 -398
  349. package/src/syntax.test.ts +0 -41
  350. package/src/syntax.ts +0 -88
  351. package/src/theme.ts +0 -46
  352. package/src/view-diff.test.ts +0 -55
  353. package/src/view-diff.ts +0 -86
  354. package/src/view.test.ts +0 -157
  355. package/src/view.ts +0 -364
@@ -0,0 +1,321 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Headless-Chromium backing for prototype review: render an HTML file to a PNG
4
+ * and resolve a click coordinate to a DOM element. puppeteer-core is imported
5
+ * lazily so plan/diff review and the test suite never load it.
6
+ */
7
+
8
+ export interface PrototypeElement {
9
+ selector: string;
10
+ quote: string;
11
+ box: ElementBox;
12
+ }
13
+
14
+ export interface ElementBox {
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ }
20
+
21
+ export interface PrototypeViewport {
22
+ width: number;
23
+ height: number;
24
+ }
25
+
26
+ export interface PrototypeRenderer {
27
+ readonly viewport: PrototypeViewport;
28
+ screenshot(): Promise<Uint8Array>;
29
+ elementAt(cssX: number, cssY: number): Promise<PrototypeElement | null>;
30
+ scrollBy(deltaY: number): Promise<boolean>;
31
+ close(): Promise<void>;
32
+ }
33
+
34
+ export interface LaunchOptions {
35
+ filePath: string;
36
+ viewport: PrototypeViewport;
37
+ deviceScaleFactor?: number;
38
+ executablePath?: string;
39
+ colorScheme?: "dark" | "light";
40
+ }
41
+
42
+ /** Cell click -> CSS pixel inside the letterboxed image, or null when outside it. */
43
+ export function imageCellToCss(
44
+ event: { x: number; y: number },
45
+ image: { x: number; y: number; width: number; height: number },
46
+ viewport: PrototypeViewport,
47
+ ): { x: number; y: number } | null {
48
+ const columnInImage = event.x - image.x;
49
+ const rowInImage = event.y - image.y;
50
+
51
+ if (
52
+ columnInImage < 0 ||
53
+ rowInImage < 0 ||
54
+ columnInImage >= image.width ||
55
+ rowInImage >= image.height
56
+ ) {
57
+ return null;
58
+ }
59
+
60
+ return {
61
+ x: ((columnInImage + 0.5) / image.width) * viewport.width,
62
+ y: ((rowInImage + 0.5) / image.height) * viewport.height,
63
+ };
64
+ }
65
+
66
+ /** CSS pixel rect -> the image cell it maps to, for anchoring overlays. */
67
+ export function cssBoxToCell(
68
+ box: ElementBox,
69
+ image: { x: number; y: number; width: number; height: number },
70
+ viewport: PrototypeViewport,
71
+ ) {
72
+ const scaleColumn = image.width / viewport.width;
73
+ const scaleRow = image.height / viewport.height;
74
+
75
+ return {
76
+ column: image.x + Math.floor(box.x * scaleColumn),
77
+ row: image.y + Math.floor(box.y * scaleRow),
78
+ columns: Math.max(1, Math.round(box.width * scaleColumn)),
79
+ rows: Math.max(1, Math.round(box.height * scaleRow)),
80
+ };
81
+ }
82
+
83
+ export type PrototypeRendererFactory = (options: LaunchOptions) => Promise<PrototypeRenderer>;
84
+
85
+ let rendererFactory: PrototypeRendererFactory | null = null;
86
+
87
+ /** Override the renderer factory so tests can inject a browserless fake. */
88
+ export function setPrototypeRendererFactory(factory: PrototypeRendererFactory | null): void {
89
+ rendererFactory = factory;
90
+ }
91
+
92
+ /** The active factory - the injected fake in tests, else real headless Chromium. */
93
+ export function prototypeRendererFactory(): PrototypeRendererFactory {
94
+ return rendererFactory ?? launchPrototypeRenderer;
95
+ }
96
+
97
+ type PuppeteerBrowser = Awaited<ReturnType<typeof import("puppeteer-core").default.launch>>;
98
+
99
+ // One Chromium per process, kept warm and reused across prototype opens: the
100
+ // launch is the dominant load cost, so each open only spawns a fresh page.
101
+ let sharedBrowser: Promise<PuppeteerBrowser> | null = null;
102
+
103
+ /** Install only the browser lifecycle used by unit tests, without launching Chrome. */
104
+ export function setPrototypeBrowserForTest(browser: Pick<PuppeteerBrowser, "close"> | null): void {
105
+ // SAFETY: tests call only closePrototypeBrowser before resetting this seam, so close is the sole
106
+ // browser member the shared slot can read while the double is installed.
107
+ sharedBrowser = browser ? Promise.resolve(browser as PuppeteerBrowser) : null;
108
+ }
109
+
110
+ async function warmBrowser(executablePath: string | undefined): Promise<PuppeteerBrowser> {
111
+ // reuse the warm browser only while it is still connected; a crashed or
112
+ // disconnected Chromium is dropped so the next open relaunches instead of
113
+ // calling newPage on a dead process forever
114
+ const cached = sharedBrowser ? await sharedBrowser.catch(() => null) : null;
115
+
116
+ if (cached && cached.connected) return cached;
117
+ const puppeteer = (await import("puppeteer-core")).default;
118
+ const launched = puppeteer.launch({
119
+ executablePath: executablePath ?? chromeExecutable(),
120
+ headless: true,
121
+ args: ["--no-sandbox", "--hide-scrollbars", "--force-color-profile=srgb"],
122
+ });
123
+
124
+ sharedBrowser = launched;
125
+ const browser = await launched.catch((error) => {
126
+ if (sharedBrowser === launched) sharedBrowser = null;
127
+ throw error;
128
+ });
129
+
130
+ browser.once("disconnected", () => {
131
+ if (sharedBrowser === launched) sharedBrowser = null;
132
+ });
133
+
134
+ return browser;
135
+ }
136
+
137
+ /**
138
+ * Close the warm Chromium so it never orphans when cueloop exits. Called from
139
+ * the shutdown path (the normal quit is not a signal, so puppeteer's own signal
140
+ * handlers do not fire). Idempotent.
141
+ */
142
+ export async function closePrototypeBrowser(): Promise<void> {
143
+ const pending = sharedBrowser;
144
+
145
+ sharedBrowser = null;
146
+ if (!pending) return;
147
+ const browser = await pending.catch(() => null);
148
+
149
+ await browser?.close().catch(() => undefined);
150
+ }
151
+
152
+ export async function launchPrototypeRenderer(options: LaunchOptions): Promise<PrototypeRenderer> {
153
+ const { pathToFileURL } = await import("node:url");
154
+ const browser = await warmBrowser(options.executablePath);
155
+ // close only the page on teardown, never the shared browser, so the next open
156
+ // reuses the warm Chromium
157
+ const page = await browser.newPage();
158
+
159
+ try {
160
+ await page.setViewport({
161
+ width: options.viewport.width,
162
+ height: options.viewport.height,
163
+ deviceScaleFactor: options.deviceScaleFactor ?? 2,
164
+ });
165
+ // match the terminal's scheme so a theme-aware mockup renders its dark or
166
+ // light variant to sit correctly on the surface it emerges into
167
+ await page.emulateMediaFeatures([
168
+ { name: "prefers-color-scheme", value: options.colorScheme ?? "light" },
169
+ ]);
170
+ // `load` waits for images and styles but skips networkidle0's fixed 500ms
171
+ // idle window, which a static local file would otherwise always pay
172
+ await page.goto(pathToFileURL(options.filePath).href, { waitUntil: "load" });
173
+ // Render the mockup on the terminal's own surface rather than on an opaque
174
+ // page: strip the root background so it never paints a square box, and the
175
+ // capture (taken with alpha, below) lets the active terminal theme show
176
+ // through around the mockup's own components - the prototype emerges into
177
+ // whatever theme is running instead of sitting in a grey card.
178
+ await page.evaluate(() => {
179
+ for (const element of [document.documentElement, document.body]) {
180
+ element?.style.setProperty("background", "transparent", "important");
181
+ }
182
+ });
183
+ } catch (error) {
184
+ await page.close().catch(() => undefined);
185
+ throw error;
186
+ }
187
+
188
+ return {
189
+ viewport: options.viewport,
190
+ async screenshot() {
191
+ // omitBackground keeps the alpha from the stripped page background, so the
192
+ // terminal composites the mockup over its own theme surface.
193
+ const buffer = await page.screenshot({
194
+ type: "png",
195
+ encoding: "binary",
196
+ omitBackground: true,
197
+ });
198
+
199
+ return new Uint8Array(buffer);
200
+ },
201
+ async elementAt(cssX, cssY) {
202
+ return page.evaluate(elementAtScript, cssX, cssY);
203
+ },
204
+ async scrollBy(deltaY) {
205
+ return page.evaluate(scrollByScript, deltaY);
206
+ },
207
+ async close() {
208
+ await page.close().catch(() => undefined);
209
+ },
210
+ };
211
+ }
212
+
213
+ /** Serialized into the page (self-contained for puppeteer): resolve the click to a component element. */
214
+ function elementAtScript(x: number, y: number) {
215
+ const SEMANTIC = new Set([
216
+ "SECTION",
217
+ "ARTICLE",
218
+ "LI",
219
+ "NAV",
220
+ "HEADER",
221
+ "FOOTER",
222
+ "ASIDE",
223
+ "FORM",
224
+ ]);
225
+ const isNamed = (element: Element): boolean =>
226
+ element.classList.length > 0 || SEMANTIC.has(element.tagName);
227
+ // a card wraps several children: climb to the nearest named container holding
228
+ // more than one child, else the nearest named ancestor, else the clicked leaf
229
+ const componentRoot = (start: Element): Element => {
230
+ let namedFallback: Element | null = null;
231
+ let current: Element | null = start;
232
+
233
+ while (current && current !== document.body) {
234
+ if (isNamed(current)) {
235
+ if (current.childElementCount > 1) return current;
236
+ namedFallback = namedFallback ?? current;
237
+ }
238
+ current = current.parentElement;
239
+ }
240
+
241
+ return namedFallback ?? start;
242
+ };
243
+ const selectorFor = (start: Element): string => {
244
+ const parts: string[] = [];
245
+ let current: Element | null = start;
246
+
247
+ while (current && current.nodeType === 1 && current !== document.documentElement) {
248
+ let part = current.tagName.toLowerCase();
249
+
250
+ if (current.id) {
251
+ parts.unshift(part + "#" + CSS.escape(current.id));
252
+ break;
253
+ }
254
+ const parent: Element | null = current.parentElement;
255
+
256
+ if (parent) {
257
+ const currentTagName = current.tagName;
258
+ const twins = [...parent.children].filter((child) => child.tagName === currentTagName);
259
+
260
+ if (twins.length > 1) part += ":nth-of-type(" + (twins.indexOf(current) + 1) + ")";
261
+ }
262
+ parts.unshift(part);
263
+ current = current.parentElement;
264
+ }
265
+
266
+ return parts.join(" > ");
267
+ };
268
+ // Prefer the nearest focusable target before falling back to its component container.
269
+ const focusableTarget = (start: Element | null): Element | null => {
270
+ for (
271
+ let element: Element | null = start;
272
+ element && element !== document.body;
273
+ element = element.parentElement
274
+ ) {
275
+ if (element instanceof HTMLElement && element.tabIndex >= 0) {
276
+ return element;
277
+ }
278
+ }
279
+
280
+ return null;
281
+ };
282
+ const hit = document.elementFromPoint(x, y);
283
+
284
+ if (!(hit instanceof Element)) return null;
285
+ const node = focusableTarget(hit) ?? componentRoot(hit);
286
+ const selector = selectorFor(node);
287
+
288
+ if (!selector) return null;
289
+ const text = (node.textContent || "").replace(/\s+/g, " ").trim();
290
+ const tag = node.tagName.toLowerCase();
291
+ const quote = text
292
+ ? text.slice(0, 80)
293
+ : node.id
294
+ ? tag + "#" + node.id
295
+ : node.classList.length
296
+ ? tag + "." + node.classList[0]
297
+ : tag;
298
+ const rect = node.getBoundingClientRect();
299
+
300
+ return { selector, quote, box: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } };
301
+ }
302
+
303
+ function scrollByScript(deltaY: number): boolean {
304
+ const before = window.scrollY;
305
+
306
+ window.scrollBy(0, deltaY);
307
+
308
+ return window.scrollY !== before;
309
+ }
310
+
311
+ /** Standard Chrome install locations by platform; puppeteer-core ships no browser. */
312
+ function chromeExecutable(): string {
313
+ if (process.platform === "darwin") {
314
+ return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
315
+ }
316
+ if (process.platform === "win32") {
317
+ return "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
318
+ }
319
+
320
+ return "/usr/bin/google-chrome";
321
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Lazy boundary for the opt-in kitty pixel prototype. The pixel path pulls in the
3
+ * kitty image writer, the headless-browser renderer, and (on first paint) puppeteer;
4
+ * none of that belongs in the runtime unless the `[experimental] prototype_pixels`
5
+ * flag is on. Importing only the TYPE here is erased at build time, so the real
6
+ * module - and its dependencies - loads only when this component actually mounts,
7
+ * which App does exclusively for a pixel prototype. This lives outside components/
8
+ * on purpose: it is an app boundary, not a visual component the story catalog covers.
9
+ */
10
+
11
+ import React, { useEffect, useState } from "react";
12
+ import type { PrototypeContentViewProps } from "./components/PrototypeContentView";
13
+
14
+ type PixelPrototypeView =
15
+ (typeof import("./components/PrototypeContentView"))["PrototypeContentView"];
16
+
17
+ export function PrototypePixels(props: PrototypeContentViewProps): React.ReactNode {
18
+ const [View, setView] = useState<PixelPrototypeView | null>(null);
19
+
20
+ useEffect(() => {
21
+ let alive = true;
22
+
23
+ void import("./components/PrototypeContentView").then((module) => {
24
+ if (alive) setView(() => module.PrototypeContentView);
25
+ });
26
+
27
+ return () => {
28
+ alive = false;
29
+ };
30
+ }, []);
31
+
32
+ if (!View) return null;
33
+
34
+ return <View {...props} />;
35
+ }
@@ -0,0 +1,117 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import { THEME_PRESETS } from "../../appearance/theme-presets";
4
+ import type { Story, StoryMeta } from "../../stories/story";
5
+ import { SettingsDialog, type SettingsCategory } from "./SettingsDialog";
6
+
7
+ export const meta: StoryMeta = { title: "Overlays/SettingsDialog" };
8
+
9
+ const CATEGORIES: SettingsCategory[] = [
10
+ {
11
+ id: "general",
12
+ name: "General",
13
+ rows: [
14
+ { key: "displayName", label: "Display name", kind: "text" },
15
+ {
16
+ key: "autoClose",
17
+ label: "Auto-close on submit",
18
+ kind: "cycle",
19
+ options: ["off", "3s", "on"],
20
+ },
21
+ ],
22
+ },
23
+ {
24
+ id: "display",
25
+ name: "Display",
26
+ rows: [
27
+ {
28
+ key: "planWidth",
29
+ label: "Plan width",
30
+ kind: "cycle",
31
+ options: ["default", "wide", "full"],
32
+ },
33
+ { key: "showLineNumbers", label: "Line numbers", kind: "toggle" },
34
+ ],
35
+ },
36
+ {
37
+ id: "appearance",
38
+ name: "Appearance",
39
+ rows: [
40
+ {
41
+ key: "theme",
42
+ label: "Theme",
43
+ kind: "cycle",
44
+ options: ["cueloop", "Rosé Pine Moon", "Nord"],
45
+ },
46
+ ],
47
+ },
48
+ ];
49
+
50
+ const VALUES = {
51
+ displayName: "amber-heron",
52
+ autoClose: "3s",
53
+ planWidth: "default",
54
+ showLineNumbers: true,
55
+ theme: "Rosé Pine Moon",
56
+ };
57
+
58
+ export const NavZone: Story = {
59
+ render: () => (
60
+ <SettingsDialog
61
+ isOpen
62
+ version="0.1.0-alpha.32"
63
+ keybindsSections={[]}
64
+ categories={CATEGORIES}
65
+ values={VALUES}
66
+ activeCategoryId="general"
67
+ activeRowIndex={0}
68
+ activeZone="nav"
69
+ onCategorySelect={() => {}}
70
+ onRowActivate={() => {}}
71
+ onClose={() => {}}
72
+ />
73
+ ),
74
+ expectedColors: [DARK.accent],
75
+ size: { width: 100, height: 30 },
76
+ };
77
+
78
+ export const BodyZone: Story = {
79
+ render: () => (
80
+ <SettingsDialog
81
+ isOpen
82
+ version="0.1.0-alpha.32"
83
+ keybindsSections={[]}
84
+ categories={CATEGORIES}
85
+ values={VALUES}
86
+ activeCategoryId="display"
87
+ activeRowIndex={1}
88
+ activeZone="body"
89
+ onCategorySelect={() => {}}
90
+ onRowActivate={() => {}}
91
+ onClose={() => {}}
92
+ />
93
+ ),
94
+ expectedColors: [DARK.green],
95
+ size: { width: 100, height: 30 },
96
+ };
97
+
98
+ export const AppearanceThemed: Story = {
99
+ render: () => (
100
+ <SettingsDialog
101
+ isOpen
102
+ version="0.1.0-alpha.32"
103
+ keybindsSections={[]}
104
+ categories={CATEGORIES}
105
+ values={VALUES}
106
+ activeCategoryId="appearance"
107
+ activeRowIndex={0}
108
+ activeZone="body"
109
+ onCategorySelect={() => {}}
110
+ onRowActivate={() => {}}
111
+ onClose={() => {}}
112
+ theme={THEME_PRESETS["rose-pine-moon"]}
113
+ />
114
+ ),
115
+ expectedColors: [THEME_PRESETS["rose-pine-moon"].accent],
116
+ size: { width: 100, height: 30 },
117
+ };
@@ -0,0 +1,183 @@
1
+ /**
2
+ * The settings overlay: a centered Dialog whose left nav lists the categories as
3
+ * first-level leaves (General, Appearance, Actions, Keybinds), and whose right
4
+ * body shows the active category's typed rows or the keybinds cheatsheet. The
5
+ * component is controlled: the caller owns the open flag, the active
6
+ * category/row/zone, and value changes, so the app's keyboard grammar (and the
7
+ * stories catalog) drive it without a second key handler inside.
8
+ */
9
+
10
+ import { ScrollArea } from "../../ui/components/ScrollArea";
11
+ import React from "react";
12
+ import { useTerminalDimensions } from "@opentui/react";
13
+ import type { Theme } from "../../appearance/theme";
14
+ import type { CheatsheetSection } from "../../keyboard/key-bindings";
15
+ import { useComponentTheme } from "../../appearance/components/theme-context";
16
+ import { Dialog } from "../../ui/components/primitives/Dialog";
17
+ import { Tree } from "../../ui/components/primitives/Tree";
18
+ import type { TreeNode } from "../../ui/components/primitives/tree-model";
19
+ import { CycleRow, TextRow, ToggleRow, type SettingsRowDescriptor } from "./SettingsRows";
20
+
21
+ export interface SettingsCategory {
22
+ id: string;
23
+ name: string;
24
+ rows: SettingsRowDescriptor[];
25
+ customBody?: React.ReactNode;
26
+ }
27
+
28
+ export type SettingsValues = Record<string, string | boolean>;
29
+
30
+ /** The category id whose right pane shows the keybinds cheatsheet rather than rows. */
31
+ const KEYBINDS_CATEGORY_ID = "keybinds";
32
+
33
+ export interface SettingsDialogProps {
34
+ isOpen: boolean;
35
+ version: string;
36
+ categories: SettingsCategory[];
37
+ values: SettingsValues;
38
+ keybindsSections: CheatsheetSection[];
39
+ activeCategoryId: string;
40
+ activeRowIndex: number;
41
+ activeZone: "nav" | "body";
42
+ onCategorySelect: (categoryId: string) => void;
43
+ onRowActivate: (row: SettingsRowDescriptor) => void;
44
+ onClose: () => void;
45
+ theme?: Theme;
46
+ }
47
+
48
+ function navTree(categories: SettingsCategory[]): TreeNode[] {
49
+ return categories.map((category) => ({ id: category.id, label: category.name }));
50
+ }
51
+
52
+ function KeybindsBody({
53
+ sections,
54
+ tokens,
55
+ }: {
56
+ sections: CheatsheetSection[];
57
+ tokens: Theme;
58
+ }): React.ReactNode {
59
+ return (
60
+ <ScrollArea>
61
+ {sections.map((section) => (
62
+ <box key={section.title} style={{ flexDirection: "column" }}>
63
+ <text fg={tokens.accent}>{section.title}</text>
64
+ {section.entries.map((entry, index) => (
65
+ <text key={`${section.title}-${index}`}>
66
+ <span fg={tokens.text}>{entry.keys.padEnd(12)}</span>
67
+ <span fg={tokens.textMuted}>{entry.label}</span>
68
+ </text>
69
+ ))}
70
+ <text> </text>
71
+ </box>
72
+ ))}
73
+ </ScrollArea>
74
+ );
75
+ }
76
+
77
+ export function SettingsDialog({
78
+ isOpen,
79
+ version,
80
+ categories,
81
+ values,
82
+ keybindsSections,
83
+ activeCategoryId,
84
+ activeRowIndex,
85
+ activeZone,
86
+ onCategorySelect,
87
+ onRowActivate,
88
+ onClose,
89
+ theme,
90
+ }: SettingsDialogProps): React.ReactNode {
91
+ const tokens = useComponentTheme(theme);
92
+ const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
93
+
94
+ if (!isOpen) return null;
95
+ const category =
96
+ categories.find((candidate) => candidate.id === activeCategoryId) ?? categories[0];
97
+
98
+ if (!category) return null;
99
+ const onKeybinds = category.id === KEYBINDS_CATEGORY_ID;
100
+
101
+ return (
102
+ <Dialog
103
+ isOpen
104
+ title=" Settings "
105
+ width={Math.min(76, terminalWidth - 6)}
106
+ height={Math.min(22, terminalHeight - 4)}
107
+ background={tokens.elevated}
108
+ onDismiss={onClose}
109
+ theme={theme}
110
+ >
111
+ <box style={{ flexDirection: "row", flexGrow: 1 }}>
112
+ <box style={{ flexDirection: "column", width: 20, paddingLeft: 1, paddingRight: 1 }}>
113
+ <Tree
114
+ nodes={navTree(categories)}
115
+ expandedIds={new Set<string>()}
116
+ selectedId={category.id}
117
+ hideIcons
118
+ selectedBackground={tokens.border}
119
+ onSelect={onCategorySelect}
120
+ onToggle={onCategorySelect}
121
+ theme={theme}
122
+ />
123
+ </box>
124
+ <box style={{ borderStyle: "single", border: ["left"], borderColor: tokens.border }} />
125
+ <box style={{ flexDirection: "column", flexGrow: 1, paddingLeft: 2, paddingRight: 1 }}>
126
+ {onKeybinds ? <KeybindsBody sections={keybindsSections} tokens={tokens} /> : null}
127
+ {onKeybinds ? null : category.customBody}
128
+ {onKeybinds
129
+ ? null
130
+ : category.rows.map((row, rowIndex) => {
131
+ const isActive = activeZone === "body" && rowIndex === activeRowIndex;
132
+ const value = values[row.key];
133
+
134
+ if (row.kind === "toggle") {
135
+ return (
136
+ <ToggleRow
137
+ key={row.key}
138
+ label={row.label}
139
+ value={value === true}
140
+ isActive={isActive}
141
+ onPress={() => onRowActivate(row)}
142
+ theme={theme}
143
+ />
144
+ );
145
+ }
146
+ if (row.kind === "cycle") {
147
+ return (
148
+ <CycleRow
149
+ key={row.key}
150
+ label={row.label}
151
+ value={String(value ?? "")}
152
+ isActive={isActive}
153
+ onPress={() => onRowActivate(row)}
154
+ theme={theme}
155
+ />
156
+ );
157
+ }
158
+
159
+ return (
160
+ <TextRow
161
+ key={row.key}
162
+ label={row.label}
163
+ value={String(value ?? "")}
164
+ isActive={isActive}
165
+ onPress={() => onRowActivate(row)}
166
+ theme={theme}
167
+ />
168
+ );
169
+ })}
170
+ </box>
171
+ </box>
172
+ <box style={{ flexDirection: "row", height: 1, paddingLeft: 1, paddingRight: 1 }}>
173
+ <text fg={tokens.textDim}>
174
+ {activeZone === "nav"
175
+ ? "j/k nav · l/tab open · esc close"
176
+ : "j/k row · l/space change · h back · esc close"}
177
+ </text>
178
+ <box style={{ flexGrow: 1 }} />
179
+ <text fg={tokens.textDim}>{`cueloop v${version}`}</text>
180
+ </box>
181
+ </Dialog>
182
+ );
183
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../stories/story";
4
+ import { CycleRow, TextRow, ToggleRow } from "./SettingsRows";
5
+
6
+ export const meta: StoryMeta = { title: "Chrome/SettingsRows" };
7
+
8
+ export const AllKinds: Story = {
9
+ render: () => (
10
+ <box style={{ flexDirection: "column", width: 50 }}>
11
+ <ToggleRow label="Line numbers" value={true} isActive={false} onPress={() => {}} />
12
+ <ToggleRow label="Hide whitespace" value={false} isActive={true} onPress={() => {}} />
13
+ <CycleRow label="Palette" value="dark" isActive={false} onPress={() => {}} />
14
+ <TextRow label="Vault path" value="~/vaults/notes" isActive={false} onPress={() => {}} />
15
+ </box>
16
+ ),
17
+ expectedColors: [DARK.green, DARK.accent, DARK.border],
18
+ };