@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,236 @@
1
+ /** The Project tree navigates by keyboard when focused: j/k move the cursor, Enter folds a folder or
2
+ * opens a file. */
3
+
4
+ import { test, expect } from "bun:test";
5
+ import React from "react";
6
+ import { testRender } from "@opentui/react/test-utils";
7
+ import { ScrollBoxRenderable, type Renderable } from "@opentui/core";
8
+ import { ProjectTreeView } from "./ProjectTreeView";
9
+ import { locateText, settle, waitForText, waitForTextGone } from "../../testing/test-support";
10
+ import { DARK } from "../../appearance/theme";
11
+
12
+ function findById(node: Renderable, id: string): Renderable | undefined {
13
+ if (node.id === id) return node;
14
+ for (const child of node.getChildren()) {
15
+ const found = findById(child, id);
16
+
17
+ if (found) return found;
18
+ }
19
+
20
+ return undefined;
21
+ }
22
+
23
+ test("Enter expands a folder, then j moves down and Enter opens the file", async () => {
24
+ const opened: string[] = [];
25
+ const setup = await testRender(
26
+ <ProjectTreeView
27
+ loadFiles={async () => ["src/a.ts", "README.md"]}
28
+ onSelectFile={(path) => opened.push(path)}
29
+ focused
30
+ theme={DARK}
31
+ />,
32
+ { width: 40, height: 12 },
33
+ );
34
+
35
+ await settle(setup);
36
+ await waitForText(setup, "src");
37
+
38
+ // cursor starts on the "src" folder; Enter expands it
39
+ setup.mockInput.pressKey("RETURN");
40
+ await waitForText(setup, "a.ts");
41
+
42
+ // j moves onto the file, Enter opens it
43
+ setup.mockInput.pressKey("j");
44
+ await settle(setup);
45
+ setup.mockInput.pressKey("RETURN");
46
+ await settle(setup);
47
+
48
+ expect(opened).toEqual(["src/a.ts"]);
49
+
50
+ setup.renderer.destroy();
51
+ });
52
+
53
+ test("keyboard navigation reveals the selected project row", async () => {
54
+ const paths = Array.from(
55
+ { length: 35 },
56
+ (_, index) => `file-${String(index).padStart(2, "0")}.ts`,
57
+ );
58
+ const setup = await testRender(
59
+ <ProjectTreeView loadFiles={async () => paths} onSelectFile={() => {}} focused theme={DARK} />,
60
+ { width: 28, height: 8 },
61
+ );
62
+
63
+ await waitForText(setup, "file-00.ts");
64
+ const scrollbox = findById(setup.renderer.root, "tree-scroll");
65
+
66
+ if (!(scrollbox instanceof ScrollBoxRenderable)) throw new Error("tree scrollbox missing");
67
+ const scrollSteps: number[] = [];
68
+
69
+ for (const [index, key] of Array.from({ length: 15 }, () => "j").entries()) {
70
+ const before = scrollbox.scrollTop;
71
+
72
+ setup.mockInput.pressKey(key);
73
+ // eslint-disable-next-line no-await-in-loop
74
+ await settle(setup);
75
+ scrollSteps.push(scrollbox.scrollTop - before);
76
+ const row = scrollbox.content.findDescendantById(
77
+ `tree-row-file-${String(index + 1).padStart(2, "0")}.ts`,
78
+ );
79
+
80
+ expect(row?.y).toBeGreaterThanOrEqual(scrollbox.viewport.y);
81
+ expect(row?.y).toBeLessThan(scrollbox.viewport.y + scrollbox.viewport.height);
82
+ }
83
+
84
+ expect(scrollbox.scrollTop).toBeGreaterThan(0);
85
+ expect(Math.max(...scrollSteps)).toBe(1);
86
+ expect(setup.captureCharFrame()).toContain("file-15.ts");
87
+ setup.renderer.destroy();
88
+ });
89
+
90
+ test("project wheel scrolling accelerates during a gesture and resets after a pause", async () => {
91
+ const paths = Array.from(
92
+ { length: 35 },
93
+ (_, index) => `file-${String(index).padStart(2, "0")}.ts`,
94
+ );
95
+ const setup = await testRender(
96
+ <ProjectTreeView loadFiles={async () => paths} onSelectFile={() => {}} theme={DARK} />,
97
+ { width: 28, height: 8 },
98
+ );
99
+
100
+ await waitForText(setup, "file-00.ts");
101
+ const scrollbox = findById(setup.renderer.root, "tree-scroll");
102
+
103
+ if (!(scrollbox instanceof ScrollBoxRenderable)) throw new Error("tree scrollbox missing");
104
+ const acceleration = scrollbox.scrollAcceleration;
105
+ const first = acceleration.tick(1000);
106
+ const burst = Array.from({ length: 10 }, (_, index) => acceleration.tick(1016 + index * 16));
107
+ const tail = acceleration.tick(1400);
108
+
109
+ expect(first).toBe(1);
110
+ expect(Math.max(...burst)).toBeGreaterThan(first);
111
+ expect(tail).toBe(1);
112
+ acceleration.reset();
113
+ await setup.mockMouse.scroll(10, 4, "down");
114
+ await settle(setup);
115
+ expect(scrollbox.scrollTop).toBeGreaterThan(0);
116
+ setup.renderer.destroy();
117
+ });
118
+
119
+ test("long project filenames stay on one row without an ellipsis", async () => {
120
+ const path = "src/ChangesFileTree.test.tsx";
121
+ const setup = await testRender(
122
+ <ProjectTreeView loadFiles={async () => [path]} onSelectFile={() => {}} focused theme={DARK} />,
123
+ { width: 22, height: 8 },
124
+ );
125
+
126
+ await waitForText(setup, "src");
127
+ setup.mockInput.pressKey("RETURN");
128
+ await settle(setup);
129
+ const frame = setup.captureCharFrame();
130
+ const row = findById(setup.renderer.root, `tree-row-${path}`);
131
+
132
+ expect(row?.height).toBe(1);
133
+ expect(frame).not.toContain("…");
134
+ expect(frame).toContain("ChangesFile");
135
+ setup.renderer.destroy();
136
+ });
137
+
138
+ test("without focus the keyboard does nothing", async () => {
139
+ const opened: string[] = [];
140
+ const setup = await testRender(
141
+ <ProjectTreeView
142
+ loadFiles={async () => ["src/a.ts"]}
143
+ onSelectFile={(path) => opened.push(path)}
144
+ theme={DARK}
145
+ />,
146
+ { width: 40, height: 12 },
147
+ );
148
+
149
+ await settle(setup);
150
+ await waitForText(setup, "src");
151
+
152
+ setup.mockInput.pressKey("RETURN");
153
+ await settle(setup);
154
+ expect(setup.captureCharFrame()).not.toContain("a.ts");
155
+ expect(opened).toEqual([]);
156
+
157
+ setup.renderer.destroy();
158
+ });
159
+
160
+ test("the empty project hint stays centered in the panel", async () => {
161
+ const setup = await testRender(
162
+ <ProjectTreeView loadFiles={async () => []} onSelectFile={() => {}} theme={DARK} />,
163
+ { width: 40, height: 12 },
164
+ );
165
+
166
+ await waitForText(setup, "empty");
167
+ const position = locateText(setup, "empty");
168
+
169
+ expect(position.column).toBeGreaterThanOrEqual(17);
170
+ expect(position.column).toBeLessThanOrEqual(18);
171
+ expect(position.row).toBeGreaterThanOrEqual(5);
172
+ expect(position.row).toBeLessThanOrEqual(6);
173
+ setup.renderer.destroy();
174
+ });
175
+
176
+ test("the visible tree removes deleted files without remounting", async () => {
177
+ let files = ["README.md", "gone.ts"];
178
+ const setup = await testRender(
179
+ <ProjectTreeView loadFiles={async () => files} onSelectFile={() => {}} theme={DARK} />,
180
+ { width: 40, height: 12 },
181
+ );
182
+
183
+ await waitForText(setup, "gone.ts");
184
+ files = ["README.md"];
185
+ await waitForTextGone(setup, "gone.ts");
186
+ expect(setup.captureCharFrame()).toContain("README.md");
187
+ setup.renderer.destroy();
188
+ });
189
+
190
+ test("refresh keeps the selected file when an earlier file disappears", async () => {
191
+ let files = ["a.ts", "b.ts", "c.ts"];
192
+ const opened: string[] = [];
193
+ const setup = await testRender(
194
+ <ProjectTreeView
195
+ loadFiles={async () => files}
196
+ onSelectFile={(path) => opened.push(path)}
197
+ focused
198
+ theme={DARK}
199
+ />,
200
+ { width: 40, height: 12 },
201
+ );
202
+
203
+ await waitForText(setup, "b.ts");
204
+ setup.mockInput.pressKey("j");
205
+ await settle(setup);
206
+ files = ["b.ts", "c.ts"];
207
+ await waitForTextGone(setup, "a.ts");
208
+ setup.mockInput.pressKey("RETURN");
209
+ await settle(setup);
210
+
211
+ expect(opened).toEqual(["b.ts"]);
212
+ setup.renderer.destroy();
213
+ });
214
+
215
+ test("a double-click requests a persistent file tab", async () => {
216
+ const opened: { path: string; persistent: boolean }[] = [];
217
+ const setup = await testRender(
218
+ <ProjectTreeView
219
+ loadFiles={async () => ["README.md"]}
220
+ onSelectFile={(path, persistent) => opened.push({ path, persistent: persistent === true })}
221
+ theme={DARK}
222
+ />,
223
+ { width: 40, height: 12 },
224
+ );
225
+
226
+ await waitForText(setup, "README.md");
227
+ const file = locateText(setup, "README.md");
228
+
229
+ await setup.mockMouse.doubleClick(file.column, file.row);
230
+ expect(opened).toEqual([
231
+ { path: "README.md", persistent: false },
232
+ { path: "README.md", persistent: false },
233
+ { path: "README.md", persistent: true },
234
+ ]);
235
+ setup.renderer.destroy();
236
+ });
@@ -0,0 +1,151 @@
1
+ // The Project pane in tree mode: the workspace's tracked files as a collapsible directory tree.
2
+ // Folders start collapsed and expand on click; a file click opens it as a tab. The caller keys this
3
+ // on the session id so switching sessions remounts it and never shows the previous repository's paths.
4
+
5
+ import { ScrollArea } from "../../ui/components/ScrollArea";
6
+ import React, { useEffect, useEffectEvent, useMemo, useState } from "react";
7
+ import { useKeyboard } from "@opentui/react";
8
+ import type { Theme } from "../../appearance/theme";
9
+ import { useComponentTheme } from "../../appearance/components/theme-context";
10
+ import { Tree } from "../../ui/components/primitives/Tree";
11
+ import { flattenTree } from "../../ui/components/primitives/tree-model";
12
+ import { buildPathTree } from "./file-tree";
13
+
14
+ const PROJECT_FILES_REFRESH_MS = 2000;
15
+
16
+ export interface ProjectTreeViewProps {
17
+ loadFiles: () => Promise<string[]>;
18
+ onSelectFile: (path: string, persistent?: boolean) => void;
19
+ focused?: boolean;
20
+ theme?: Theme;
21
+ }
22
+
23
+ export function ProjectTreeView({
24
+ loadFiles,
25
+ onSelectFile,
26
+ focused = false,
27
+ theme,
28
+ }: ProjectTreeViewProps): React.ReactNode {
29
+ const tokens = useComponentTheme(theme);
30
+ const [paths, setPaths] = useState<readonly string[] | null>(null);
31
+ const [expandedIds, setExpandedIds] = useState<ReadonlySet<string>>(new Set());
32
+ const [cursorId, setCursorId] = useState<string | null>(null);
33
+ const nodes = useMemo(() => (paths ? buildPathTree(paths) : []), [paths]);
34
+ const rows = useMemo(() => flattenTree(nodes, { expandedIds }), [nodes, expandedIds]);
35
+ const cursorIndex = Math.max(
36
+ 0,
37
+ rows.findIndex((row) => row.id === cursorId),
38
+ );
39
+ const loadLatestFiles = useEffectEvent(loadFiles);
40
+
41
+ const toggle = (id: string): void =>
42
+ setExpandedIds((current) => {
43
+ const next = new Set(current);
44
+
45
+ if (next.has(id)) next.delete(id);
46
+ else next.add(id);
47
+
48
+ return next;
49
+ });
50
+
51
+ useKeyboard((key) => {
52
+ if (!focused || rows.length === 0) return;
53
+ if (key.name === "j" || key.name === "down")
54
+ return setCursorId(rows[Math.min(cursorIndex + 1, rows.length - 1)]!.id);
55
+ if (key.name === "k" || key.name === "up")
56
+ return setCursorId(rows[Math.max(cursorIndex - 1, 0)]!.id);
57
+ const row = rows[cursorIndex];
58
+
59
+ if (!row) return;
60
+ if (key.name === "return" || key.name === "enter")
61
+ return row.isFolder ? toggle(row.id) : onSelectFile(row.id, true);
62
+ if (key.name === "l" && row.isFolder && !expandedIds.has(row.id)) return toggle(row.id);
63
+ if (key.name === "h" && row.isFolder && expandedIds.has(row.id)) return toggle(row.id);
64
+ });
65
+
66
+ useEffect(() => {
67
+ let cancelled = false;
68
+ let requestInFlight = false;
69
+
70
+ const refreshFiles = async (): Promise<void> => {
71
+ if (requestInFlight) return;
72
+ requestInFlight = true;
73
+
74
+ try {
75
+ const files = await loadLatestFiles();
76
+
77
+ if (!cancelled) {
78
+ setPaths((previous) =>
79
+ previous?.length === files.length &&
80
+ previous.every((path, index) => path === files[index])
81
+ ? previous
82
+ : files,
83
+ );
84
+ }
85
+ } catch {
86
+ if (!cancelled) setPaths((previous) => previous ?? []);
87
+ } finally {
88
+ requestInFlight = false;
89
+ }
90
+ };
91
+
92
+ void refreshFiles();
93
+ const timer = setInterval(() => void refreshFiles(), PROJECT_FILES_REFRESH_MS);
94
+
95
+ return () => {
96
+ cancelled = true;
97
+ clearInterval(timer);
98
+ };
99
+ }, []);
100
+
101
+ if (paths === null) {
102
+ return (
103
+ <box
104
+ style={{
105
+ flexGrow: 1,
106
+ flexDirection: "column",
107
+ alignItems: "center",
108
+ justifyContent: "center",
109
+ }}
110
+ >
111
+ <text fg={tokens.textDim}>loading...</text>
112
+ </box>
113
+ );
114
+ }
115
+ if (paths.length === 0) {
116
+ return (
117
+ <box
118
+ style={{
119
+ flexGrow: 1,
120
+ flexDirection: "column",
121
+ alignItems: "center",
122
+ justifyContent: "center",
123
+ }}
124
+ >
125
+ <text fg={tokens.textDim}>empty</text>
126
+ </box>
127
+ );
128
+ }
129
+
130
+ return (
131
+ <ScrollArea
132
+ id="tree-scroll"
133
+ revealId={focused ? rows[cursorIndex]?.id : undefined}
134
+ gestureWheel
135
+ >
136
+ <Tree
137
+ nodes={nodes}
138
+ expandedIds={expandedIds}
139
+ selectedId={focused ? rows[cursorIndex]?.id : undefined}
140
+ singleLine
141
+ onSelect={(id) => {
142
+ setCursorId(id);
143
+ onSelectFile(id);
144
+ }}
145
+ onDoubleSelect={(id) => onSelectFile(id, true)}
146
+ onToggle={toggle}
147
+ theme={theme}
148
+ />
149
+ </ScrollArea>
150
+ );
151
+ }
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../stories/story";
4
+ import { TreePane } from "./TreePane";
5
+ import type { TreeRow } from "../../thread/tree-view";
6
+
7
+ export const meta: StoryMeta = { title: "Surfaces/TreePane" };
8
+
9
+ const callbacks = {
10
+ onSelect: () => {},
11
+ onGo: () => {},
12
+ onBranch: () => {},
13
+ onLabel: () => {},
14
+ onFork: () => {},
15
+ onForkAndShare: () => {},
16
+ };
17
+
18
+ /** rev1 -> comment (labelled, a branch hangs off it) -> the branch's comment -> rev2 as main's tip. */
19
+ const ROWS: TreeRow[] = [
20
+ {
21
+ entryId: "e1",
22
+ depth: 0,
23
+ glyph: "◉",
24
+ text: "revision 1",
25
+ onPath: true,
26
+ tips: [],
27
+ isCurrentTip: false,
28
+ },
29
+ {
30
+ entryId: "e2",
31
+ depth: 0,
32
+ glyph: "·",
33
+ text: "comment",
34
+ onPath: true,
35
+ tips: [],
36
+ isCurrentTip: false,
37
+ label: "start",
38
+ },
39
+ {
40
+ entryId: "e3",
41
+ depth: 1,
42
+ glyph: "·",
43
+ text: "comment",
44
+ onPath: false,
45
+ tips: ["alt"],
46
+ isCurrentTip: false,
47
+ },
48
+ {
49
+ entryId: "e4",
50
+ depth: 0,
51
+ glyph: "◉",
52
+ text: "revision 2",
53
+ onPath: true,
54
+ tips: ["main"],
55
+ isCurrentTip: true,
56
+ },
57
+ ];
58
+
59
+ /** The rail expects a height-constrained parent, like the app's main row. */
60
+ function PaneFrame({ children }: { children: React.ReactNode }): React.ReactNode {
61
+ return (
62
+ <box
63
+ style={{ flexDirection: "column", width: 40, height: "100%", backgroundColor: DARK.panel }}
64
+ >
65
+ {children}
66
+ </box>
67
+ );
68
+ }
69
+
70
+ export const OwnerWithBranch: Story = {
71
+ render: () => (
72
+ <PaneFrame>
73
+ <TreePane rows={ROWS} selectedEntryId="e2" canMove canFork {...callbacks} />
74
+ </PaneFrame>
75
+ ),
76
+ expectedColors: [DARK.accent],
77
+ size: { width: 40, height: 16 },
78
+ };
79
+
80
+ export const CollaboratorReadsOnly: Story = {
81
+ render: () => (
82
+ <PaneFrame>
83
+ <TreePane rows={ROWS} canMove={false} canFork={false} {...callbacks} />
84
+ </PaneFrame>
85
+ ),
86
+ size: { width: 40, height: 16 },
87
+ };
88
+
89
+ /** A resolved review: the owner can still fork, not move. */
90
+ export const ResolvedForkOnly: Story = {
91
+ render: () => (
92
+ <PaneFrame>
93
+ <TreePane rows={ROWS} canMove={false} canFork {...callbacks} />
94
+ </PaneFrame>
95
+ ),
96
+ size: { width: 40, height: 16 },
97
+ };
98
+
99
+ export const NoHistory: Story = {
100
+ render: () => (
101
+ <PaneFrame>
102
+ <TreePane rows={[]} canMove canFork {...callbacks} />
103
+ </PaneFrame>
104
+ ),
105
+ size: { width: 40, height: 8 },
106
+ };
@@ -0,0 +1,155 @@
1
+ /**
2
+ * The rail's Tree tab: the session's history as one row per entry. The active
3
+ * path reads bright, everything else dim; a branch tip carries its name, the
4
+ * current tip a marker, a checkpoint its label. Clicking a row selects it,
5
+ * clicking the selected row goes there; the buttons underneath are the mouse
6
+ * path to the same primitives the option chords reach.
7
+ */
8
+
9
+ import { ScrollArea } from "../../ui/components/ScrollArea";
10
+ import React, { useEffect, useRef } from "react";
11
+ import { createTextAttributes, type ScrollBoxRenderable } from "@opentui/core";
12
+ import type { Theme } from "../../appearance/theme";
13
+ import type { TreeRow } from "../../thread/tree-view";
14
+ import { useComponentTheme } from "../../appearance/components/theme-context";
15
+ import { Button } from "../../ui/components/primitives/Button";
16
+ import { Toolbar } from "../../ui/components/primitives/Toolbar";
17
+
18
+ export interface TreePaneProps {
19
+ rows: TreeRow[];
20
+ selectedEntryId?: string;
21
+ canMove: boolean;
22
+ canFork: boolean;
23
+ onSelect: (entryId: string) => void;
24
+ onGo: (entryId: string) => void;
25
+ onBranch: () => void;
26
+ onLabel: () => void;
27
+ onFork: () => void;
28
+ onForkAndShare: () => void;
29
+ theme?: Theme;
30
+ }
31
+
32
+ const DIM = createTextAttributes({ dim: true });
33
+ const BOLD = createTextAttributes({ bold: true });
34
+
35
+ /** The marker column: the current tip stands out, every other row keeps its kind's glyph. */
36
+ function markerFor(row: TreeRow): string {
37
+ return row.isCurrentTip ? "●" : row.glyph;
38
+ }
39
+
40
+ /** What follows the entry's name: the branches whose tip it is, and its checkpoint. */
41
+ function trailerFor(row: TreeRow): string {
42
+ const parts: string[] = [];
43
+
44
+ if (row.tips.length) parts.push(`← ${row.tips.join(", ")}`);
45
+ if (row.label !== undefined) parts.push(`⚑ ${row.label}`);
46
+
47
+ return parts.length ? ` ${parts.join(" ")}` : "";
48
+ }
49
+
50
+ export function TreePane({
51
+ rows,
52
+ selectedEntryId,
53
+ canMove,
54
+ canFork,
55
+ onSelect,
56
+ onGo,
57
+ onBranch,
58
+ onLabel,
59
+ onFork,
60
+ onForkAndShare,
61
+ theme,
62
+ }: TreePaneProps): React.ReactNode {
63
+ const tokens = useComponentTheme(theme);
64
+ const scrollRef = useRef<ScrollBoxRenderable | null>(null);
65
+ const revealId = selectedEntryId ?? rows.find((row) => row.isCurrentTip)?.entryId;
66
+
67
+ // the trunk draws last, so a long history opens with its tip below the fold: keep the row in view
68
+ useEffect(() => {
69
+ if (revealId === undefined) return;
70
+ try {
71
+ scrollRef.current?.scrollChildIntoView(`tree-row-${revealId}`);
72
+ } catch {
73
+ // reveal is best-effort; the selection is already correct
74
+ }
75
+ }, [revealId]);
76
+
77
+ if (rows.length === 0)
78
+ return (
79
+ <box style={{ flexGrow: 1, alignItems: "center", justifyContent: "center" }}>
80
+ <text fg={tokens.textDim}>no history yet</text>
81
+ </box>
82
+ );
83
+
84
+ return (
85
+ <box style={{ flexGrow: 1, flexDirection: "column" }}>
86
+ <ScrollArea scrollRef={scrollRef}>
87
+ {rows.map((row) => {
88
+ const selected = row.entryId === selectedEntryId;
89
+ const foreground = row.onPath ? tokens.text : tokens.textDim;
90
+
91
+ return (
92
+ <box
93
+ key={row.entryId}
94
+ id={`tree-row-${row.entryId}`}
95
+ style={{
96
+ flexDirection: "row",
97
+ paddingLeft: 1 + row.depth * 2,
98
+ backgroundColor: selected ? tokens.elevated : undefined,
99
+ }}
100
+ onMouseUp={() => (selected && canMove ? onGo(row.entryId) : onSelect(row.entryId))}
101
+ >
102
+ <text
103
+ fg={row.isCurrentTip ? tokens.accent : foreground}
104
+ attributes={row.onPath ? undefined : DIM}
105
+ >
106
+ {markerFor(row)} {row.text}
107
+ </text>
108
+ <text fg={tokens.textMuted} attributes={row.label === undefined ? undefined : BOLD}>
109
+ {trailerFor(row)}
110
+ </text>
111
+ </box>
112
+ );
113
+ })}
114
+ </ScrollArea>
115
+ {canMove || canFork ? (
116
+ <box style={{ paddingLeft: 1 }}>
117
+ <Toolbar>
118
+ {canMove ? (
119
+ <Button
120
+ onPress={() => {
121
+ if (selectedEntryId !== undefined) onGo(selectedEntryId);
122
+ }}
123
+ isDisabled={selectedEntryId === undefined}
124
+ marginRight={1}
125
+ theme={theme}
126
+ >
127
+ Go
128
+ </Button>
129
+ ) : null}
130
+ {canMove ? (
131
+ <Button onPress={onBranch} marginRight={1} theme={theme}>
132
+ Branch
133
+ </Button>
134
+ ) : null}
135
+ {canMove ? (
136
+ <Button onPress={onLabel} marginRight={1} theme={theme}>
137
+ Label
138
+ </Button>
139
+ ) : null}
140
+ {canFork ? (
141
+ <Button onPress={onFork} marginRight={1} theme={theme}>
142
+ Fork
143
+ </Button>
144
+ ) : null}
145
+ {canFork ? (
146
+ <Button onPress={onForkAndShare} theme={theme}>
147
+ Fork+share
148
+ </Button>
149
+ ) : null}
150
+ </Toolbar>
151
+ </box>
152
+ ) : null}
153
+ </box>
154
+ );
155
+ }
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { DARK } from "../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../stories/story";
4
+ import { WelcomePlayground } from "./WelcomePlayground";
5
+
6
+ export const meta: StoryMeta = { title: "Surfaces/WelcomePlayground" };
7
+
8
+ export const Welcome: Story = {
9
+ render: () => <WelcomePlayground quickActions={[]} theme={DARK} />,
10
+ expectedColors: [DARK.textDim],
11
+ size: { width: 72, height: 20 },
12
+ };