@cueloop/client 0.1.0-alpha.0 → 0.1.0-alpha.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/native/build-pty.sh +51 -0
  2. package/native/build.sh +82 -0
  3. package/native/darwin-arm64/libcuelooppty.dylib +0 -0
  4. package/native/darwin-arm64/libcueloopvt.dylib +0 -0
  5. package/native/src/pty.zig +193 -0
  6. package/native/src/shim.zig +114 -0
  7. package/package.json +41 -27
  8. package/src/annotations/annotation-palette.ts +62 -0
  9. package/src/annotations/components/AnnotationCards.stories.tsx +86 -0
  10. package/src/annotations/components/AnnotationCards.test.tsx +42 -0
  11. package/src/annotations/components/AnnotationCards.tsx +364 -0
  12. package/src/annotations/components/DiscussionMarkerRail.stories.tsx +54 -0
  13. package/src/annotations/components/DiscussionMarkerRail.tsx +186 -0
  14. package/src/annotations/discussions.ts +77 -0
  15. package/src/annotations/thread-selection.test.ts +210 -0
  16. package/src/annotations/thread-selection.ts +242 -0
  17. package/src/annotations/use-annotation-surface.tsx +1238 -0
  18. package/src/app/App.appearance.test.tsx +53 -0
  19. package/src/app/App.autoclose.test.tsx +144 -0
  20. package/src/app/App.deleted-scroll.test.tsx +187 -0
  21. package/src/app/App.diff.test.tsx +313 -0
  22. package/src/app/App.export.test.tsx +105 -0
  23. package/src/app/App.focus.test.ts +89 -0
  24. package/src/app/App.identity.test.tsx +84 -0
  25. package/src/app/App.plan-render.test.tsx +209 -0
  26. package/src/app/App.plan-review.test.tsx +428 -0
  27. package/src/app/App.prototype-markdown.test.tsx +128 -0
  28. package/src/app/App.prototype.test.tsx +176 -0
  29. package/src/app/App.readonly.test.tsx +210 -0
  30. package/src/app/App.regression.test.tsx +99 -0
  31. package/src/app/App.rendering.test.tsx +134 -0
  32. package/src/app/App.share-dialog.test.tsx +177 -0
  33. package/src/app/App.submit-confirm.test.tsx +267 -0
  34. package/src/app/App.test.tsx +496 -0
  35. package/src/app/App.thread-agent.test.ts +12 -0
  36. package/src/app/App.toast.test.tsx +124 -0
  37. package/src/app/App.tsx +1650 -0
  38. package/src/app/App.visual.test.tsx +59 -0
  39. package/src/app/App.walk.test.tsx +299 -0
  40. package/src/app/App.workbench.test.tsx +1149 -0
  41. package/src/app/app-key-state.ts +61 -0
  42. package/src/app/app-screens.tsx +606 -0
  43. package/src/app/app-view-model.ts +168 -0
  44. package/src/app/components/AppHeader.stories.tsx +50 -0
  45. package/src/app/components/AppHeader.tsx +81 -0
  46. package/src/app/components/AppShell.brand.test.tsx +58 -0
  47. package/src/app/components/AppShell.extensions.test.tsx +191 -0
  48. package/src/app/components/AppShell.stories.tsx +89 -0
  49. package/src/app/components/AppShell.tsx +358 -0
  50. package/src/app/components/AppShell.zoom.test.tsx +149 -0
  51. package/src/app/components/Breadcrumb.stories.tsx +19 -0
  52. package/src/app/components/Breadcrumb.tsx +45 -0
  53. package/src/app/components/MenuBar.stories.tsx +28 -0
  54. package/src/app/components/MenuBar.tsx +44 -0
  55. package/src/app/components/PaneGrid.stories.tsx +84 -0
  56. package/src/app/components/PaneGrid.tsx +128 -0
  57. package/src/app/components/PanelColumn.stories.tsx +25 -0
  58. package/src/app/components/PanelColumn.tsx +93 -0
  59. package/src/app/components/ShellHeader.stories.tsx +50 -0
  60. package/src/app/components/ShellHeader.tsx +137 -0
  61. package/src/app/components/WorkspacePanels.stories.tsx +36 -0
  62. package/src/app/components/WorkspacePanels.tsx +120 -0
  63. package/src/app/components/pages/Workspace.stories.tsx +181 -0
  64. package/src/app/intent-dispatch.test.ts +728 -0
  65. package/src/app/intent-dispatch.ts +687 -0
  66. package/src/app/launch-layout.test.ts +78 -0
  67. package/src/app/launch-layout.ts +57 -0
  68. package/src/app/ready-signal.test.ts +85 -0
  69. package/src/app/ready-signal.ts +52 -0
  70. package/src/app/run.ts +109 -0
  71. package/src/{serve.test.ts → app/serve.test.ts} +24 -12
  72. package/src/app/serve.ts +111 -0
  73. package/src/app/version.ts +5 -0
  74. package/src/appearance/components/theme-context.stories.tsx +46 -0
  75. package/src/appearance/components/theme-context.tsx +37 -0
  76. package/src/appearance/theme-presets.test.ts +51 -0
  77. package/src/appearance/theme-presets.ts +219 -0
  78. package/src/appearance/theme.ts +112 -0
  79. package/src/appearance/visual/contrast.test.ts +35 -0
  80. package/src/appearance/visual/contrast.ts +33 -0
  81. package/src/diff/components/AnnotatableFileView.stories.tsx +42 -0
  82. package/src/diff/components/AnnotatableFileView.test.tsx +157 -0
  83. package/src/diff/components/AnnotatableFileView.tsx +141 -0
  84. package/src/diff/components/BareWorkbenchFileView.stories.tsx +27 -0
  85. package/src/diff/components/BareWorkbenchFileView.tsx +63 -0
  86. package/src/diff/components/DiffContentView.scroll.test.tsx +434 -0
  87. package/src/diff/components/DiffContentView.split.test.tsx +140 -0
  88. package/src/diff/components/DiffContentView.stories.tsx +80 -0
  89. package/src/diff/components/DiffContentView.syntax.test.tsx +82 -0
  90. package/src/diff/components/DiffContentView.tsx +1055 -0
  91. package/src/diff/components/WalkWizard.stories.tsx +100 -0
  92. package/src/diff/components/WalkWizard.tsx +146 -0
  93. package/src/diff/components/diff-content-view-layout.test.ts +198 -0
  94. package/src/diff/components/diff-content-view-layout.ts +84 -0
  95. package/src/diff/diff-fold.test.ts +78 -0
  96. package/src/diff/diff-fold.ts +126 -0
  97. package/src/diff/diff-hunk-curate.test.ts +315 -0
  98. package/src/diff/diff-hunk-curate.ts +152 -0
  99. package/src/diff/diff-intraline.test.ts +239 -0
  100. package/src/diff/diff-intraline.ts +238 -0
  101. package/src/diff/diff-syntax.test.ts +125 -0
  102. package/src/diff/diff-syntax.ts +186 -0
  103. package/src/diff/split-diff.test.ts +110 -0
  104. package/src/diff/split-diff.ts +144 -0
  105. package/src/diff/view-diff.test.ts +325 -0
  106. package/src/diff/view-diff.ts +522 -0
  107. package/src/diff/walk.test.ts +121 -0
  108. package/src/diff/walk.ts +73 -0
  109. package/src/index.ts +31 -2
  110. package/src/integrations/attribution.test.ts +31 -0
  111. package/src/integrations/attribution.ts +42 -0
  112. package/src/integrations/client-extension-registry.test.tsx +61 -0
  113. package/src/integrations/client-extension-registry.ts +159 -0
  114. package/src/integrations/clipboard.test.ts +23 -0
  115. package/src/integrations/clipboard.ts +33 -0
  116. package/src/integrations/components/ExtensionZone.stories.tsx +28 -0
  117. package/src/integrations/components/ExtensionZone.tsx +145 -0
  118. package/src/integrations/components/ShareDialog.stories.tsx +121 -0
  119. package/src/integrations/components/ShareDialog.tsx +550 -0
  120. package/src/integrations/components/share-dialog-store.ts +55 -0
  121. package/src/integrations/editor.test.ts +149 -0
  122. package/src/integrations/editor.ts +160 -0
  123. package/src/integrations/github-identity.test.ts +58 -0
  124. package/src/integrations/github-identity.ts +41 -0
  125. package/src/integrations/herdr.test.ts +43 -0
  126. package/src/integrations/herdr.ts +38 -0
  127. package/src/integrations/integrations.test.ts +41 -0
  128. package/src/integrations/integrations.ts +32 -0
  129. package/src/integrations/pasted-image.test.ts +67 -0
  130. package/src/integrations/pasted-image.ts +60 -0
  131. package/src/integrations/share.ts +238 -0
  132. package/src/keyboard/components/CompletionOverlay.stories.tsx +37 -0
  133. package/src/keyboard/components/CompletionOverlay.tsx +75 -0
  134. package/src/keyboard/components/HandleChipsInput.stories.tsx +24 -0
  135. package/src/keyboard/components/HandleChipsInput.test.tsx +35 -0
  136. package/src/keyboard/components/HandleChipsInput.tsx +143 -0
  137. package/src/keyboard/components/KeybindsDialog.stories.tsx +31 -0
  138. package/src/keyboard/components/KeybindsDialog.tsx +103 -0
  139. package/src/keyboard/components/NavModeHint.stories.tsx +40 -0
  140. package/src/keyboard/components/NavModeHint.tsx +31 -0
  141. package/src/keyboard/components/SlashComposer.stories.tsx +37 -0
  142. package/src/keyboard/components/SlashComposer.tsx +87 -0
  143. package/src/keyboard/key-bindings.test.ts +156 -0
  144. package/src/keyboard/key-bindings.ts +459 -0
  145. package/src/keyboard/keymap.test.ts +611 -0
  146. package/src/keyboard/keymap.ts +395 -0
  147. package/src/keyboard/skills.test.ts +74 -0
  148. package/src/keyboard/skills.ts +75 -0
  149. package/src/keyboard/slash-palette.test.ts +103 -0
  150. package/src/keyboard/slash-palette.ts +129 -0
  151. package/src/keyboard/thread-chords.test.ts +79 -0
  152. package/src/keyboard/thread-chords.ts +163 -0
  153. package/src/keyboard/use-shared-keyboard.ts +55 -0
  154. package/src/markdown/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  155. package/src/markdown/components/MarkdownGridBlock.stories.tsx +33 -0
  156. package/src/markdown/components/MarkdownGridBlock.tsx +72 -0
  157. package/src/markdown/components/MarkdownThreadEditor.stories.tsx +15 -0
  158. package/src/markdown/components/MarkdownThreadEditor.test.tsx +98 -0
  159. package/src/markdown/components/MarkdownThreadEditor.tsx +160 -0
  160. package/src/markdown/components/ThreadView.selection.test.tsx +951 -0
  161. package/src/markdown/components/ThreadView.slash.test.tsx +146 -0
  162. package/src/markdown/components/ThreadView.stories.tsx +26 -0
  163. package/src/markdown/components/ThreadView.tsx +560 -0
  164. package/src/markdown/mark-runs.ts +121 -0
  165. package/src/markdown/markdown-editor-syntax-style.ts +50 -0
  166. package/src/markdown/markdown-grid.test.ts +101 -0
  167. package/src/markdown/markdown-grid.ts +203 -0
  168. package/src/markdown/markdown-highlight.test.ts +61 -0
  169. package/src/markdown/markdown-highlight.ts +124 -0
  170. package/src/markdown/thread-code-syntax.ts +45 -0
  171. package/src/markdown/use-thread-body-editing.tsx +54 -0
  172. package/src/markdown/view-plan.regression.test.ts +60 -0
  173. package/src/markdown/view-plan.test.ts +545 -0
  174. package/src/markdown/view-plan.ts +1009 -0
  175. package/src/performance/otlp-trace.test.ts +119 -0
  176. package/src/performance/otlp-trace.ts +134 -0
  177. package/src/performance/perf-report.ts +18 -0
  178. package/src/performance/perf-timings.test.ts +18 -0
  179. package/src/performance/perf-timings.ts +39 -0
  180. package/src/prototype/components/PrototypeContentView.stories.tsx +19 -0
  181. package/src/prototype/components/PrototypeContentView.tsx +442 -0
  182. package/src/prototype/prototype-browser.test.ts +81 -0
  183. package/src/prototype/prototype-browser.ts +321 -0
  184. package/src/prototype/prototype-pixels.tsx +35 -0
  185. package/src/settings/components/SettingsDialog.stories.tsx +117 -0
  186. package/src/settings/components/SettingsDialog.tsx +183 -0
  187. package/src/settings/components/SettingsRows.stories.tsx +18 -0
  188. package/src/settings/components/SettingsRows.tsx +112 -0
  189. package/src/settings/components/quick-actions-editor.stories.tsx +58 -0
  190. package/src/settings/components/quick-actions-editor.test.tsx +120 -0
  191. package/src/settings/components/quick-actions-editor.tsx +222 -0
  192. package/src/settings/config-identity.test.ts +78 -0
  193. package/src/settings/config.test.ts +676 -0
  194. package/src/settings/config.ts +628 -0
  195. package/src/settings/use-settings-dialog.tsx +351 -0
  196. package/src/stories/__snapshots__/stories.test.tsx.snap +2358 -0
  197. package/src/stories/stories-app.tsx +196 -0
  198. package/src/stories/stories.test.tsx +74 -0
  199. package/src/stories/story-fixtures.ts +102 -0
  200. package/src/stories/story-tree.test.ts +108 -0
  201. package/src/stories/story.ts +154 -0
  202. package/src/terminal/ansi-screen.test.ts +28 -0
  203. package/src/terminal/ansi-screen.ts +72 -0
  204. package/src/terminal/components/AnsiScreen.stories.tsx +12 -0
  205. package/src/terminal/components/AnsiScreen.tsx +28 -0
  206. package/src/terminal/components/terminal-pane.test.tsx +40 -0
  207. package/src/terminal/components/terminal-pane.ts +196 -0
  208. package/src/terminal/ghostty-terminal.test.ts +85 -0
  209. package/src/terminal/ghostty-terminal.ts +197 -0
  210. package/src/terminal/kitty-image.test.ts +66 -0
  211. package/src/terminal/kitty-image.ts +137 -0
  212. package/src/terminal/pty.test.ts +81 -0
  213. package/src/terminal/pty.ts +246 -0
  214. package/src/testing/test-support.test.ts +264 -0
  215. package/src/testing/test-support.ts +367 -0
  216. package/src/thread/agent-continuation.test.ts +32 -0
  217. package/src/thread/agent-continuation.ts +13 -0
  218. package/src/thread/agent-transcript.test.ts +161 -0
  219. package/src/thread/agent-transcript.ts +336 -0
  220. package/src/thread/components/AgentConfigControls.stories.tsx +39 -0
  221. package/src/thread/components/AgentConfigControls.test.tsx +112 -0
  222. package/src/thread/components/AgentConfigControls.tsx +114 -0
  223. package/src/thread/components/AgentThreadPane.stories.tsx +105 -0
  224. package/src/thread/components/AgentThreadPane.test.tsx +829 -0
  225. package/src/thread/components/AgentThreadPane.tsx +367 -0
  226. package/src/thread/components/ThreadFooter.stories.tsx +30 -0
  227. package/src/thread/components/ThreadFooter.test.tsx +49 -0
  228. package/src/thread/components/ThreadFooter.tsx +77 -0
  229. package/src/thread/components/ThreadTree.stories.tsx +46 -0
  230. package/src/thread/components/ThreadTree.test.tsx +41 -0
  231. package/src/thread/components/ThreadTree.tsx +267 -0
  232. package/src/thread/components/thread-tree.test.ts +137 -0
  233. package/src/thread/components/thread-tree.ts +112 -0
  234. package/src/thread/thread-controller.curate.test.ts +502 -0
  235. package/src/thread/thread-controller.derivation.test.ts +62 -0
  236. package/src/thread/thread-controller.live-diff.test.ts +357 -0
  237. package/src/thread/thread-controller.optimistic.test.ts +69 -0
  238. package/src/thread/thread-controller.share.test.ts +573 -0
  239. package/src/thread/thread-controller.switch-cache.test.ts +174 -0
  240. package/src/thread/thread-controller.tree.test.ts +263 -0
  241. package/src/thread/thread-controller.ts +2018 -0
  242. package/src/thread/tree-view.test.ts +128 -0
  243. package/src/thread/tree-view.ts +146 -0
  244. package/src/thread/use-thread-agent.test.tsx +208 -0
  245. package/src/thread/use-thread-agent.ts +111 -0
  246. package/src/ui/components/CodeBlock.stories.tsx +42 -0
  247. package/src/ui/components/CodeBlock.tsx +80 -0
  248. package/src/ui/components/ConfirmCard.stories.tsx +37 -0
  249. package/src/ui/components/ConfirmCard.tsx +151 -0
  250. package/src/ui/components/ConfirmDialog.stories.tsx +21 -0
  251. package/src/ui/components/ConfirmDialog.tsx +72 -0
  252. package/src/ui/components/OverlayScrollbar.stories.tsx +33 -0
  253. package/src/ui/components/OverlayScrollbar.test.tsx +139 -0
  254. package/src/ui/components/OverlayScrollbar.tsx +129 -0
  255. package/src/ui/components/PromptDialog.stories.tsx +36 -0
  256. package/src/ui/components/PromptDialog.test.tsx +65 -0
  257. package/src/ui/components/PromptDialog.tsx +99 -0
  258. package/src/ui/components/RootOverlay.stories.tsx +45 -0
  259. package/src/ui/components/RootOverlay.tsx +49 -0
  260. package/src/ui/components/ScrollArea.stories.tsx +20 -0
  261. package/src/ui/components/ScrollArea.tsx +61 -0
  262. package/src/ui/components/Toast.stories.tsx +19 -0
  263. package/src/ui/components/Toast.tsx +54 -0
  264. package/src/ui/components/Tooltip.stories.tsx +26 -0
  265. package/src/ui/components/Tooltip.tsx +72 -0
  266. package/src/ui/components/menu-control.stories.tsx +35 -0
  267. package/src/ui/components/menu-control.tsx +43 -0
  268. package/src/ui/components/primitives/Button.stories.tsx +32 -0
  269. package/src/ui/components/primitives/Button.tsx +57 -0
  270. package/src/ui/components/primitives/Card.stories.tsx +25 -0
  271. package/src/ui/components/primitives/Card.tsx +62 -0
  272. package/src/ui/components/primitives/Dialog.stories.tsx +24 -0
  273. package/src/ui/components/primitives/Dialog.test.tsx +38 -0
  274. package/src/ui/components/primitives/Dialog.tsx +69 -0
  275. package/src/ui/components/primitives/DialogActions.stories.tsx +20 -0
  276. package/src/ui/components/primitives/DialogActions.tsx +39 -0
  277. package/src/ui/components/primitives/IconButton.stories.tsx +36 -0
  278. package/src/ui/components/primitives/IconButton.tsx +58 -0
  279. package/src/ui/components/primitives/StatusBar.stories.tsx +11 -0
  280. package/src/ui/components/primitives/StatusBar.tsx +23 -0
  281. package/src/ui/components/primitives/Switch.stories.tsx +17 -0
  282. package/src/ui/components/primitives/Switch.tsx +32 -0
  283. package/src/ui/components/primitives/Tabs.stories.tsx +29 -0
  284. package/src/ui/components/primitives/Tabs.tsx +101 -0
  285. package/src/ui/components/primitives/Toolbar.stories.tsx +17 -0
  286. package/src/ui/components/primitives/Toolbar.tsx +14 -0
  287. package/src/ui/components/primitives/Tree.stories.tsx +124 -0
  288. package/src/ui/components/primitives/Tree.tsx +133 -0
  289. package/src/ui/components/primitives/frame.test.ts +12 -0
  290. package/src/ui/components/primitives/frame.ts +17 -0
  291. package/src/ui/components/primitives/icons.ts +61 -0
  292. package/src/ui/components/primitives/tree-model.test.ts +99 -0
  293. package/src/ui/components/primitives/tree-model.ts +137 -0
  294. package/src/ui/components/syntax-highlight.ts +133 -0
  295. package/src/ui/components/truncate-text.ts +7 -0
  296. package/src/ui/components/truncate-title.test.ts +31 -0
  297. package/src/ui/components/truncate-title.ts +15 -0
  298. package/src/ui/use-frame-measure.ts +74 -0
  299. package/src/ui/use-terminal-virtualizer.ts +259 -0
  300. package/src/workbench/components/ChangesColumn.stories.tsx +44 -0
  301. package/src/workbench/components/ChangesColumn.tsx +203 -0
  302. package/src/workbench/components/ChangesFileTree.test.tsx +181 -0
  303. package/src/workbench/components/EditorGrid.split.test.tsx +118 -0
  304. package/src/workbench/components/EditorGrid.stories.tsx +86 -0
  305. package/src/workbench/components/EditorGrid.tabs.test.tsx +118 -0
  306. package/src/workbench/components/EditorGrid.tsx +544 -0
  307. package/src/workbench/components/EditorGrid.zoom.test.tsx +132 -0
  308. package/src/workbench/components/FileContentsView.stories.tsx +16 -0
  309. package/src/workbench/components/FileContentsView.test.tsx +29 -0
  310. package/src/workbench/components/FileContentsView.tsx +100 -0
  311. package/src/workbench/components/GridTabContent.actions.test.tsx +151 -0
  312. package/src/workbench/components/GridTabContent.stories.tsx +59 -0
  313. package/src/workbench/components/GridTabContent.tsx +193 -0
  314. package/src/workbench/components/ProjectTreeView.stories.tsx +25 -0
  315. package/src/workbench/components/ProjectTreeView.test.tsx +236 -0
  316. package/src/workbench/components/ProjectTreeView.tsx +151 -0
  317. package/src/workbench/components/TreePane.stories.tsx +106 -0
  318. package/src/workbench/components/TreePane.tsx +155 -0
  319. package/src/workbench/components/WelcomePlayground.stories.tsx +12 -0
  320. package/src/workbench/components/WelcomePlayground.test.tsx +83 -0
  321. package/src/workbench/components/WelcomePlayground.tsx +138 -0
  322. package/src/workbench/components/editor-grid.test.ts +184 -0
  323. package/src/workbench/components/editor-grid.ts +281 -0
  324. package/src/workbench/components/file-tree.test.ts +60 -0
  325. package/src/workbench/components/file-tree.ts +112 -0
  326. package/src/workbench/use-changes-workbench.ts +205 -0
  327. package/src/workbench/use-remember-layout.ts +31 -0
  328. package/src/workbench/use-repo-changes.ts +91 -0
  329. package/src/workbench/workbench-snapshot.test.ts +99 -0
  330. package/src/workbench/workbench-snapshot.ts +59 -0
  331. package/src/App.diff.test.tsx +0 -99
  332. package/src/App.export.test.tsx +0 -80
  333. package/src/App.readonly.test.tsx +0 -148
  334. package/src/App.test.tsx +0 -217
  335. package/src/App.tsx +0 -0
  336. package/src/config.test.ts +0 -116
  337. package/src/config.ts +0 -113
  338. package/src/editor.ts +0 -34
  339. package/src/run.ts +0 -28
  340. package/src/serve.ts +0 -75
  341. package/src/theme.ts +0 -46
  342. package/src/view-diff.test.ts +0 -55
  343. package/src/view-diff.ts +0 -86
  344. package/src/view.test.ts +0 -157
  345. package/src/view.ts +0 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Word-button: a pressable label. `onPress` unifies mouse and (future)
3
+ * keyboard activation; `isDisabled` swallows presses without changing the
4
+ * rendered label, so read-only affordances keep their place in the layout.
5
+ */
6
+
7
+ import React from "react";
8
+ import type { MouseEvent } from "@opentui/core";
9
+ import type { Theme } from "../../../appearance/theme";
10
+ import { useComponentTheme } from "../../../appearance/components/theme-context";
11
+
12
+ export interface ButtonProps {
13
+ onPress: () => void;
14
+ isDisabled?: boolean;
15
+ variant?: "solid" | "plain" | "accent-text";
16
+ foreground?: string;
17
+ marginRight?: number;
18
+ theme?: Theme;
19
+ children: string;
20
+ }
21
+
22
+ export function Button({
23
+ onPress,
24
+ isDisabled = false,
25
+ variant = "plain",
26
+ foreground: foregroundOverride,
27
+ marginRight,
28
+ theme,
29
+ children,
30
+ }: ButtonProps): React.ReactNode {
31
+ const tokens = useComponentTheme(theme);
32
+ // a disabled solid button reads as muted, so it never looks like a live call to action
33
+ const backgroundColor =
34
+ variant === "solid" ? (isDisabled ? tokens.border : tokens.accent) : undefined;
35
+ const foreground =
36
+ foregroundOverride ??
37
+ (variant === "solid"
38
+ ? isDisabled
39
+ ? tokens.textMuted
40
+ : tokens.accentInk
41
+ : variant === "accent-text"
42
+ ? tokens.accent
43
+ : tokens.textDim);
44
+
45
+ return (
46
+ <box
47
+ style={{ backgroundColor, marginRight }}
48
+ onMouseUp={(event: MouseEvent) => {
49
+ // Consume the press so it never bubbles to an ancestor's onMouseUp (e.g. a card).
50
+ event.stopPropagation();
51
+ if (!isDisabled) onPress();
52
+ }}
53
+ >
54
+ <text fg={foreground}>{children}</text>
55
+ </box>
56
+ );
57
+ }
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { DARK } from "../../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../../stories/story";
4
+ import { Card } from "./Card";
5
+
6
+ export const meta: StoryMeta = { title: "Primitives/Card" };
7
+
8
+ export const Titled: Story = {
9
+ render: () => (
10
+ <Card title=" a titled card " contentRows={2}>
11
+ <text>first content row</text>
12
+ <text>second content row</text>
13
+ </Card>
14
+ ),
15
+ expectedColors: [DARK.border, DARK.elevated],
16
+ };
17
+
18
+ export const AccentBorder: Story = {
19
+ render: () => (
20
+ <Card title=" accented " contentRows={1} borderColor={DARK.accent}>
21
+ <text>content</text>
22
+ </Card>
23
+ ),
24
+ expectedColors: [DARK.accent],
25
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Bordered box with an optional title. The card owns the
3
+ * height-derivation rule: its height is always the declared content row
4
+ * count plus the two border rows, so layout math and the mounted box can
5
+ * never drift apart - border-collapse bugs die here.
6
+ */
7
+
8
+ import React from "react";
9
+ import type { Theme } from "../../../appearance/theme";
10
+ import { useComponentTheme } from "../../../appearance/components/theme-context";
11
+ import { FRAME_BORDER_STYLE, frameTitle } from "./frame";
12
+
13
+ export interface CardProps {
14
+ title?: string;
15
+ contentRows: number;
16
+ width?: number;
17
+ borderColor?: string;
18
+ backgroundColor?: string;
19
+ marginLeft?: number;
20
+ marginRight?: number;
21
+ theme?: Theme;
22
+ children: React.ReactNode;
23
+ }
24
+
25
+ /** The one height rule: content rows plus the top and bottom border rows. */
26
+ export function cardHeight(contentRows: number): number {
27
+ return contentRows + 2;
28
+ }
29
+
30
+ export function Card({
31
+ title,
32
+ contentRows,
33
+ width,
34
+ borderColor,
35
+ backgroundColor,
36
+ marginLeft,
37
+ marginRight,
38
+ theme,
39
+ children,
40
+ }: CardProps): React.ReactNode {
41
+ const tokens = useComponentTheme(theme);
42
+
43
+ return (
44
+ <box
45
+ style={{
46
+ height: cardHeight(contentRows),
47
+ width,
48
+ marginLeft,
49
+ marginRight,
50
+ border: true,
51
+ borderStyle: FRAME_BORDER_STYLE,
52
+ borderColor: borderColor ?? tokens.border,
53
+ backgroundColor: backgroundColor ?? tokens.elevated,
54
+ flexDirection: "column",
55
+ paddingLeft: 1,
56
+ }}
57
+ title={frameTitle(title)}
58
+ >
59
+ {children}
60
+ </box>
61
+ );
62
+ }
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+ import { DARK } from "../../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../../stories/story";
4
+ import { Dialog } from "./Dialog";
5
+
6
+ export const meta: StoryMeta = { title: "Primitives/Dialog" };
7
+
8
+ export const Open: Story = {
9
+ render: () => (
10
+ <Dialog isOpen title=" a modal " width={40} height={8}>
11
+ <text>dialog body content</text>
12
+ </Dialog>
13
+ ),
14
+ expectedColors: [DARK.accent],
15
+ size: { width: 60, height: 16 },
16
+ };
17
+
18
+ export const Closed: Story = {
19
+ render: () => (
20
+ <Dialog isOpen={false} title=" hidden " width={40} height={8}>
21
+ <text>never rendered</text>
22
+ </Dialog>
23
+ ),
24
+ };
@@ -0,0 +1,38 @@
1
+ /** The dialog dismisses on a backdrop click (outside the panel) but never on a click inside it. */
2
+
3
+ import { test, expect } from "bun:test";
4
+ import React from "react";
5
+ import { testRender } from "@opentui/react/test-utils";
6
+ import { Dialog } from "./Dialog";
7
+ import { settle } from "../../../testing/test-support";
8
+ import { DARK } from "../../../appearance/theme";
9
+
10
+ test("clicking the backdrop dismisses; clicking inside the panel does not", async () => {
11
+ let dismissed = 0;
12
+ const setup = await testRender(
13
+ <Dialog
14
+ isOpen
15
+ title=" test "
16
+ width={20}
17
+ height={5}
18
+ background={DARK.elevated}
19
+ onDismiss={() => (dismissed += 1)}
20
+ theme={DARK}
21
+ >
22
+ <text>body</text>
23
+ </Dialog>,
24
+ { width: 60, height: 20 },
25
+ );
26
+
27
+ await settle(setup);
28
+
29
+ // the top-left corner is backdrop, outside the centered panel
30
+ await setup.mockMouse.click(0, 0);
31
+ expect(dismissed).toBe(1);
32
+
33
+ // the screen centre lands inside the panel, so it must not dismiss
34
+ await setup.mockMouse.click(30, 10);
35
+ expect(dismissed).toBe(1);
36
+
37
+ setup.renderer.destroy();
38
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Centered modal: a full-screen layer (backdrop token, transparent by
3
+ * default) centers a bordered, unpainted panel over the session - the frame
4
+ * delineates the dialog, the content behind stays visible.
5
+ * `isOpen` keeps the mount decision with the caller-visible prop vocabulary.
6
+ */
7
+
8
+ import React from "react";
9
+ import type { Theme } from "../../../appearance/theme";
10
+ import { useComponentTheme } from "../../../appearance/components/theme-context";
11
+ import { FRAME_BORDER_STYLE, frameTitle } from "./frame";
12
+
13
+ export interface DialogProps {
14
+ isOpen: boolean;
15
+ title?: string;
16
+ width: number;
17
+ height: number;
18
+ background?: string;
19
+ onDismiss?: () => void;
20
+ theme?: Theme;
21
+ children: React.ReactNode;
22
+ }
23
+
24
+ export function Dialog({
25
+ isOpen,
26
+ title,
27
+ width,
28
+ height,
29
+ background,
30
+ onDismiss,
31
+ theme,
32
+ children,
33
+ }: DialogProps): React.ReactNode {
34
+ const tokens = useComponentTheme(theme);
35
+
36
+ if (!isOpen) return null;
37
+
38
+ return (
39
+ <box
40
+ onMouseUp={onDismiss}
41
+ style={{
42
+ position: "absolute",
43
+ left: 0,
44
+ top: 0,
45
+ width: "100%",
46
+ height: "100%",
47
+ backgroundColor: tokens.backdrop,
48
+ alignItems: "center",
49
+ justifyContent: "center",
50
+ }}
51
+ >
52
+ <box
53
+ onMouseUp={(event) => event.stopPropagation()}
54
+ style={{
55
+ width,
56
+ height,
57
+ border: true,
58
+ borderStyle: FRAME_BORDER_STYLE,
59
+ borderColor: tokens.accent,
60
+ backgroundColor: background,
61
+ flexDirection: "column",
62
+ }}
63
+ title={frameTitle(title)}
64
+ >
65
+ {children}
66
+ </box>
67
+ </box>
68
+ );
69
+ }
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { DARK } from "../../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../../stories/story";
4
+ import { DialogActions } from "./DialogActions";
5
+
6
+ export const meta: StoryMeta = { title: "Primitives/DialogActions" };
7
+
8
+ const noop = () => {};
9
+
10
+ export const SaveCancel: Story = {
11
+ render: () => <DialogActions confirmLabel="save" onConfirm={noop} onCancel={noop} />,
12
+ expectedColors: [DARK.accent],
13
+ size: { width: 40, height: 3 },
14
+ };
15
+
16
+ export const DeleteCancel: Story = {
17
+ render: () => <DialogActions confirmLabel="delete" onConfirm={noop} onCancel={noop} />,
18
+ expectedColors: [DARK.accent],
19
+ size: { width: 40, height: 3 },
20
+ };
@@ -0,0 +1,39 @@
1
+ /** The one action row every overlay uses: a solid confirm word-button and a plain cancel, lowercased. */
2
+
3
+ import React from "react";
4
+ import type { Theme } from "../../../appearance/theme";
5
+ import { Toolbar } from "./Toolbar";
6
+ import { Button } from "./Button";
7
+
8
+ export interface DialogActionsProps {
9
+ confirmLabel: string;
10
+ onConfirm: () => void;
11
+ onCancel: () => void;
12
+ cancelLabel?: string;
13
+ confirmDisabled?: boolean;
14
+ theme?: Theme;
15
+ }
16
+
17
+ export function DialogActions({
18
+ confirmLabel,
19
+ onConfirm,
20
+ onCancel,
21
+ cancelLabel = "cancel",
22
+ confirmDisabled = false,
23
+ theme,
24
+ }: DialogActionsProps): React.ReactNode {
25
+ return (
26
+ <Toolbar>
27
+ <Button
28
+ variant="solid"
29
+ marginRight={2}
30
+ onPress={onConfirm}
31
+ isDisabled={confirmDisabled}
32
+ theme={theme}
33
+ >
34
+ {` ${confirmLabel} `}
35
+ </Button>
36
+ <Button onPress={onCancel} theme={theme}>{` ${cancelLabel} `}</Button>
37
+ </Toolbar>
38
+ );
39
+ }
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { DARK } from "../../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../../stories/story";
4
+ import { IconButton } from "./IconButton";
5
+ import { NERD } from "./icons";
6
+
7
+ export const meta: StoryMeta = { title: "Primitives/IconButton" };
8
+
9
+ function Row({ children }: { children: React.ReactNode }): React.ReactNode {
10
+ return (
11
+ <box style={{ flexDirection: "row", padding: 1, backgroundColor: DARK.panel }}>{children}</box>
12
+ );
13
+ }
14
+
15
+ export const States: Story = {
16
+ render: () => (
17
+ <Row>
18
+ <IconButton glyph={NERD.settings} onPress={() => {}} marginRight={3} />
19
+ <IconButton glyph={NERD.listTree} active onPress={() => {}} marginRight={3} />
20
+ <IconButton glyph={NERD.diff} disabled marginRight={3} />
21
+ <IconButton glyph={NERD.submit} onPress={() => {}} />
22
+ </Row>
23
+ ),
24
+ expectedColors: [DARK.accent, DARK.textMuted, DARK.textDim],
25
+ size: { width: 40, height: 3 },
26
+ };
27
+
28
+ export const Submit: Story = {
29
+ render: () => (
30
+ <Row>
31
+ <IconButton glyph={NERD.submit} active onPress={() => {}} />
32
+ </Row>
33
+ ),
34
+ expectedColors: [DARK.accent],
35
+ size: { width: 20, height: 3 },
36
+ };
@@ -0,0 +1,58 @@
1
+ // A clickable single-glyph button for header and footer controls: an icon in a
2
+ // box that calls onPress, painted in the brand accent when active. alignSelf
3
+ // center keeps it on the same row as sibling text or icons.
4
+
5
+ import React from "react";
6
+ import type { MouseEvent as TerminalMouseEvent } from "@opentui/core";
7
+ import { DARK, type Theme } from "../../../appearance/theme";
8
+ import { useTooltip } from "../Tooltip";
9
+
10
+ export interface IconButtonProps {
11
+ glyph: string;
12
+ onPress?: () => void;
13
+ active?: boolean;
14
+ disabled?: boolean;
15
+ color?: string;
16
+ tip?: string;
17
+ marginLeft?: number;
18
+ marginRight?: number;
19
+ theme?: Theme;
20
+ }
21
+
22
+ export function IconButton({
23
+ glyph,
24
+ onPress,
25
+ active,
26
+ disabled,
27
+ color,
28
+ tip,
29
+ marginLeft,
30
+ marginRight,
31
+ theme,
32
+ }: IconButtonProps): React.ReactNode {
33
+ const tokens = theme ?? DARK;
34
+ const resolved = color ?? (disabled ? tokens.textDim : active ? tokens.accent : tokens.textMuted);
35
+ const { showTooltip, hideTooltip } = useTooltip();
36
+ // a button press is its own action - never let it bubble to a clickable row behind it
37
+ const handleMouseUp = (event: TerminalMouseEvent): void => {
38
+ event.stopPropagation();
39
+ hideTooltip();
40
+ onPress?.();
41
+ };
42
+
43
+ return (
44
+ <box
45
+ onMouseUp={disabled ? undefined : handleMouseUp}
46
+ onMouseOver={tip ? (event) => showTooltip(tip, event.x, event.y) : undefined}
47
+ onMouseOut={tip ? hideTooltip : undefined}
48
+ style={{
49
+ flexShrink: 0,
50
+ alignSelf: "center",
51
+ marginLeft,
52
+ marginRight,
53
+ }}
54
+ >
55
+ <text fg={resolved}>{glyph}</text>
56
+ </box>
57
+ );
58
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { DARK } from "../../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../../stories/story";
4
+ import { StatusBar } from "./StatusBar";
5
+
6
+ export const meta: StoryMeta = { title: "Primitives/StatusBar" };
7
+
8
+ export const HintLine: Story = {
9
+ render: () => <StatusBar>j/k move · v span · c comment · q quit</StatusBar>,
10
+ expectedColors: [DARK.textDim],
11
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * One-row panel bar for the status/hint line. Content renders as dim text;
3
+ * callers pass richer spans as children when a segment needs another tone.
4
+ */
5
+
6
+ import React from "react";
7
+ import type { Theme } from "../../../appearance/theme";
8
+ import { useComponentTheme } from "../../../appearance/components/theme-context";
9
+
10
+ export interface StatusBarProps {
11
+ theme?: Theme;
12
+ children: React.ReactNode;
13
+ }
14
+
15
+ export function StatusBar({ theme, children }: StatusBarProps): React.ReactNode {
16
+ const tokens = useComponentTheme(theme);
17
+
18
+ return (
19
+ <box style={{ height: 1, backgroundColor: tokens.panel, paddingLeft: 1 }}>
20
+ <text fg={tokens.textDim}>{children}</text>
21
+ </box>
22
+ );
23
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { DARK } from "../../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../../stories/story";
4
+ import { Switch } from "./Switch";
5
+
6
+ export const meta: StoryMeta = { title: "Primitives/Switch" };
7
+
8
+ export const On: Story = {
9
+ render: () => <Switch on onToggle={() => {}} theme={DARK} />,
10
+ expectedColors: [DARK.accent],
11
+ size: { width: 8, height: 1 },
12
+ };
13
+
14
+ export const Off: Story = {
15
+ render: () => <Switch on={false} onToggle={() => {}} theme={DARK} />,
16
+ size: { width: 8, height: 1 },
17
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * On/off switch: a pill with a knob that sits right (accent) when on and left
3
+ * (dim) when off. `onToggle` unifies a mouse click and keyboard activation, so
4
+ * the dialog grammar and a pointer both flip it.
5
+ */
6
+
7
+ import React from "react";
8
+ import type { MouseEvent } from "@opentui/core";
9
+ import type { Theme } from "../../../appearance/theme";
10
+ import { useComponentTheme } from "../../../appearance/components/theme-context";
11
+
12
+ export interface SwitchProps {
13
+ on: boolean;
14
+ onToggle: () => void;
15
+ theme?: Theme;
16
+ }
17
+
18
+ export function Switch({ on, onToggle, theme }: SwitchProps): React.ReactNode {
19
+ const tokens = useComponentTheme(theme);
20
+
21
+ return (
22
+ <box
23
+ onMouseUp={(event: MouseEvent) => {
24
+ event.stopPropagation();
25
+ onToggle();
26
+ }}
27
+ style={{ flexShrink: 0 }}
28
+ >
29
+ <text fg={on ? tokens.accent : tokens.textDim}>{on ? "( ●)" : "(● )"}</text>
30
+ </box>
31
+ );
32
+ }
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { DARK } from "../../../appearance/theme";
3
+ import type { Story, StoryMeta } from "../../../stories/story";
4
+ import { Tab, TabList, Tabs } from "./Tabs";
5
+
6
+ export const meta: StoryMeta = { title: "Primitives/Tabs" };
7
+
8
+ export const TwoTabs: Story = {
9
+ render: () => (
10
+ <Tabs selectedKey="review" onSelectionChange={() => {}}>
11
+ <TabList>
12
+ <Tab id="review">Review (2)</Tab>
13
+ <Tab id="agent">Agent</Tab>
14
+ </TabList>
15
+ </Tabs>
16
+ ),
17
+ expectedColors: [DARK.accent, DARK.textDim],
18
+ };
19
+
20
+ export const SecondSelected: Story = {
21
+ render: () => (
22
+ <Tabs selectedKey="agent" onSelectionChange={() => {}}>
23
+ <TabList>
24
+ <Tab id="review">Review (0)</Tab>
25
+ <Tab id="agent">Agent</Tab>
26
+ </TabList>
27
+ </Tabs>
28
+ ),
29
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Tabs / TabList / Tab: a transparent, full-width rounded strip of clickable
3
+ * text tabs. The composition follows the ARIA tabs pattern: Tabs owns the
4
+ * controlled selection (`selectedKey` + `onSelectionChange`), TabList renders
5
+ * the strip, Tab declares one item. Selection stays controlled from outside so
6
+ * the keyboard grammar remains the key owner; each tab click sets the key.
7
+ */
8
+
9
+ import React, { createContext, useContext } from "react";
10
+ import type { Theme } from "../../../appearance/theme";
11
+ import { useComponentTheme } from "../../../appearance/components/theme-context";
12
+ import { FRAME_BORDER_STYLE } from "./frame";
13
+
14
+ interface TabsContextValue {
15
+ selectedKey: string;
16
+ onSelectionChange: (key: string) => void;
17
+ selectedColor?: string;
18
+ theme?: Theme;
19
+ }
20
+
21
+ const TabsContext = createContext<TabsContextValue | null>(null);
22
+
23
+ export interface TabsProps {
24
+ selectedKey: string;
25
+ onSelectionChange: (key: string) => void;
26
+ selectedColor?: string;
27
+ theme?: Theme;
28
+ children: React.ReactNode;
29
+ }
30
+
31
+ export function Tabs({
32
+ selectedKey,
33
+ onSelectionChange,
34
+ selectedColor,
35
+ theme,
36
+ children,
37
+ }: TabsProps): React.ReactNode {
38
+ return (
39
+ <TabsContext.Provider value={{ selectedKey, onSelectionChange, selectedColor, theme }}>
40
+ {children}
41
+ </TabsContext.Provider>
42
+ );
43
+ }
44
+
45
+ export interface TabProps {
46
+ id: string;
47
+ children: string;
48
+ }
49
+
50
+ /** Declarative item: TabList collects Tab children into the native strip. */
51
+ export function Tab(_props: TabProps): React.ReactNode {
52
+ return null;
53
+ }
54
+
55
+ export interface TabListProps {
56
+ children: React.ReactNode;
57
+ }
58
+
59
+ export function TabList({ children }: TabListProps): React.ReactNode {
60
+ const tabs = useContext(TabsContext);
61
+
62
+ if (!tabs) throw new Error("TabList must render inside Tabs");
63
+ const tokens = useComponentTheme(tabs.theme);
64
+ const items: { id: string; label: string }[] = [];
65
+
66
+ React.Children.forEach(children, (child) => {
67
+ if (React.isValidElement<TabProps>(child)) {
68
+ items.push({ id: child.props.id, label: child.props.children });
69
+ }
70
+ });
71
+
72
+ return (
73
+ <box
74
+ style={{
75
+ width: "100%",
76
+ height: 3,
77
+ border: true,
78
+ borderStyle: FRAME_BORDER_STYLE,
79
+ borderColor: tokens.text,
80
+ flexDirection: "row",
81
+ paddingLeft: 1,
82
+ }}
83
+ >
84
+ {items.map((item) => {
85
+ const selected = item.id === tabs.selectedKey;
86
+
87
+ return (
88
+ <box
89
+ key={item.id}
90
+ style={{ marginRight: 2 }}
91
+ onMouseUp={() => tabs.onSelectionChange(item.id)}
92
+ >
93
+ <text fg={selected ? (tabs.selectedColor ?? tokens.accent) : tokens.textDim}>
94
+ {item.label}
95
+ </text>
96
+ </box>
97
+ );
98
+ })}
99
+ </box>
100
+ );
101
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import type { Story, StoryMeta } from "../../../stories/story";
3
+ import { Button } from "./Button";
4
+ import { Toolbar } from "./Toolbar";
5
+
6
+ export const meta: StoryMeta = { title: "Primitives/Toolbar" };
7
+
8
+ export const ActionRow: Story = {
9
+ render: () => (
10
+ <Toolbar>
11
+ <Button variant="solid" marginRight={2} onPress={() => {}}>
12
+ {" Submit "}
13
+ </Button>
14
+ <Button onPress={() => {}}>{" Cancel "}</Button>
15
+ </Toolbar>
16
+ ),
17
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Horizontal action row: children flow left to right on one row. The classic
3
+ * home of Button groups (Save/Cancel, Submit/Cancel).
4
+ */
5
+
6
+ import React from "react";
7
+
8
+ export interface ToolbarProps {
9
+ children: React.ReactNode;
10
+ }
11
+
12
+ export function Toolbar({ children }: ToolbarProps): React.ReactNode {
13
+ return <box style={{ flexDirection: "row", height: 1 }}>{children}</box>;
14
+ }