@cueloop/client 0.1.0-alpha.9 → 0.1.0-alpha.91

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 (316) 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/App.appearance.test.tsx +53 -0
  9. package/src/App.autoclose.test.tsx +77 -51
  10. package/src/App.diff.test.tsx +259 -45
  11. package/src/App.export.test.tsx +58 -41
  12. package/src/App.focus.test.ts +89 -0
  13. package/src/App.identity.test.tsx +78 -0
  14. package/src/App.plan-render.test.tsx +209 -0
  15. package/src/App.plan-review.test.tsx +330 -161
  16. package/src/App.prototype-markdown.test.tsx +128 -0
  17. package/src/App.prototype.test.tsx +176 -0
  18. package/src/App.readonly.test.tsx +129 -67
  19. package/src/App.regression.test.tsx +94 -0
  20. package/src/App.rendering.test.tsx +61 -35
  21. package/src/App.share-dialog.test.tsx +172 -0
  22. package/src/App.submit-confirm.test.tsx +264 -0
  23. package/src/App.test.tsx +402 -126
  24. package/src/App.toast.test.tsx +98 -0
  25. package/src/App.tsx +1406 -1060
  26. package/src/App.visual.test.tsx +57 -0
  27. package/src/App.walk.test.tsx +299 -0
  28. package/src/App.workbench.test.tsx +787 -0
  29. package/src/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  30. package/src/annotation-palette.ts +62 -0
  31. package/src/ansi-screen.test.ts +28 -0
  32. package/src/ansi-screen.ts +72 -0
  33. package/src/app-key-state.ts +61 -0
  34. package/src/app-screens.tsx +577 -0
  35. package/src/app-view-model.ts +168 -0
  36. package/src/attribution.test.ts +31 -0
  37. package/src/attribution.ts +42 -0
  38. package/src/client-extension-registry.test.tsx +61 -0
  39. package/src/client-extension-registry.ts +159 -0
  40. package/src/clipboard.test.ts +23 -0
  41. package/src/clipboard.ts +33 -0
  42. package/src/components/AnnotatableFileView.stories.tsx +42 -0
  43. package/src/components/AnnotatableFileView.test.tsx +102 -0
  44. package/src/components/AnnotatableFileView.tsx +134 -0
  45. package/src/components/AnnotationCards.stories.tsx +86 -0
  46. package/src/components/AnnotationCards.tsx +341 -0
  47. package/src/components/AnsiScreen.stories.tsx +12 -0
  48. package/src/components/AnsiScreen.tsx +28 -0
  49. package/src/components/AppHeader.stories.tsx +50 -0
  50. package/src/components/AppHeader.tsx +83 -0
  51. package/src/components/AppShell.brand.test.tsx +58 -0
  52. package/src/components/AppShell.extensions.test.tsx +191 -0
  53. package/src/components/AppShell.stories.tsx +89 -0
  54. package/src/components/AppShell.tsx +362 -0
  55. package/src/components/AppShell.zoom.test.tsx +135 -0
  56. package/src/components/BareWorkbenchFileView.stories.tsx +27 -0
  57. package/src/components/BareWorkbenchFileView.tsx +64 -0
  58. package/src/components/Breadcrumb.stories.tsx +19 -0
  59. package/src/components/Breadcrumb.tsx +45 -0
  60. package/src/components/ChangesColumn.stories.tsx +44 -0
  61. package/src/components/ChangesColumn.tsx +199 -0
  62. package/src/components/ChangesFileTree.test.tsx +61 -0
  63. package/src/components/CodeBlock.stories.tsx +42 -0
  64. package/src/components/CodeBlock.tsx +84 -0
  65. package/src/components/CompletionOverlay.stories.tsx +37 -0
  66. package/src/components/CompletionOverlay.tsx +77 -0
  67. package/src/components/ConfirmCard.stories.tsx +37 -0
  68. package/src/components/ConfirmCard.tsx +150 -0
  69. package/src/components/ConfirmDialog.stories.tsx +21 -0
  70. package/src/components/ConfirmDialog.tsx +72 -0
  71. package/src/components/DiffContentView.scroll.test.tsx +149 -0
  72. package/src/components/DiffContentView.split.test.tsx +140 -0
  73. package/src/components/DiffContentView.stories.tsx +80 -0
  74. package/src/components/DiffContentView.syntax.test.tsx +82 -0
  75. package/src/components/DiffContentView.tsx +906 -0
  76. package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
  77. package/src/components/DiscussionMarkerRail.tsx +186 -0
  78. package/src/components/EditorGrid.stories.tsx +59 -0
  79. package/src/components/EditorGrid.tabs.test.tsx +118 -0
  80. package/src/components/EditorGrid.tsx +426 -0
  81. package/src/components/EditorGrid.zoom.test.tsx +68 -0
  82. package/src/components/ExtensionZone.stories.tsx +28 -0
  83. package/src/components/ExtensionZone.tsx +145 -0
  84. package/src/components/FileContentsView.stories.tsx +16 -0
  85. package/src/components/FileContentsView.test.tsx +29 -0
  86. package/src/components/FileContentsView.tsx +98 -0
  87. package/src/components/GridTabContent.stories.tsx +59 -0
  88. package/src/components/GridTabContent.tsx +182 -0
  89. package/src/components/HandleChipsInput.stories.tsx +24 -0
  90. package/src/components/HandleChipsInput.test.tsx +35 -0
  91. package/src/components/HandleChipsInput.tsx +145 -0
  92. package/src/components/KeybindsDialog.stories.tsx +31 -0
  93. package/src/components/KeybindsDialog.tsx +103 -0
  94. package/src/components/MarkdownGridBlock.stories.tsx +33 -0
  95. package/src/components/MarkdownGridBlock.tsx +72 -0
  96. package/src/components/MarkdownThreadEditor.stories.tsx +15 -0
  97. package/src/components/MarkdownThreadEditor.test.tsx +98 -0
  98. package/src/components/MarkdownThreadEditor.tsx +162 -0
  99. package/src/components/MenuBar.stories.tsx +28 -0
  100. package/src/components/MenuBar.tsx +44 -0
  101. package/src/components/NavModeHint.stories.tsx +40 -0
  102. package/src/components/NavModeHint.tsx +31 -0
  103. package/src/components/OverlayScrollbar.stories.tsx +33 -0
  104. package/src/components/OverlayScrollbar.test.tsx +139 -0
  105. package/src/components/OverlayScrollbar.tsx +129 -0
  106. package/src/components/PaneGrid.stories.tsx +84 -0
  107. package/src/components/PaneGrid.tsx +128 -0
  108. package/src/components/PanelColumn.stories.tsx +25 -0
  109. package/src/components/PanelColumn.tsx +98 -0
  110. package/src/components/ProjectTreeView.stories.tsx +25 -0
  111. package/src/components/ProjectTreeView.test.tsx +75 -0
  112. package/src/components/ProjectTreeView.tsx +125 -0
  113. package/src/components/PromptDialog.stories.tsx +36 -0
  114. package/src/components/PromptDialog.test.tsx +61 -0
  115. package/src/components/PromptDialog.tsx +99 -0
  116. package/src/components/PrototypeContentView.stories.tsx +19 -0
  117. package/src/components/PrototypeContentView.tsx +446 -0
  118. package/src/components/RootOverlay.stories.tsx +45 -0
  119. package/src/components/RootOverlay.tsx +48 -0
  120. package/src/components/ScrollArea.stories.tsx +20 -0
  121. package/src/components/ScrollArea.tsx +44 -0
  122. package/src/components/SettingsDialog.stories.tsx +117 -0
  123. package/src/components/SettingsDialog.tsx +186 -0
  124. package/src/components/SettingsRows.stories.tsx +18 -0
  125. package/src/components/SettingsRows.tsx +112 -0
  126. package/src/components/ShareDialog.stories.tsx +121 -0
  127. package/src/components/ShareDialog.tsx +552 -0
  128. package/src/components/ShellHeader.stories.tsx +50 -0
  129. package/src/components/ShellHeader.tsx +137 -0
  130. package/src/components/SlashComposer.stories.tsx +37 -0
  131. package/src/components/SlashComposer.tsx +87 -0
  132. package/src/components/ThreadFooter.stories.tsx +30 -0
  133. package/src/components/ThreadFooter.test.tsx +47 -0
  134. package/src/components/ThreadFooter.tsx +76 -0
  135. package/src/components/ThreadTree.stories.tsx +46 -0
  136. package/src/components/ThreadTree.tsx +255 -0
  137. package/src/components/ThreadView.selection.test.tsx +951 -0
  138. package/src/components/ThreadView.slash.test.tsx +146 -0
  139. package/src/components/ThreadView.stories.tsx +26 -0
  140. package/src/components/ThreadView.tsx +525 -0
  141. package/src/components/Toast.stories.tsx +19 -0
  142. package/src/components/Toast.tsx +52 -0
  143. package/src/components/Tooltip.stories.tsx +26 -0
  144. package/src/components/Tooltip.tsx +72 -0
  145. package/src/components/TreePane.stories.tsx +106 -0
  146. package/src/components/TreePane.tsx +157 -0
  147. package/src/components/WalkWizard.stories.tsx +100 -0
  148. package/src/components/WalkWizard.tsx +150 -0
  149. package/src/components/WelcomePlayground.stories.tsx +12 -0
  150. package/src/components/WelcomePlayground.test.tsx +49 -0
  151. package/src/components/WelcomePlayground.tsx +139 -0
  152. package/src/components/WorkspacePanels.stories.tsx +36 -0
  153. package/src/components/WorkspacePanels.tsx +116 -0
  154. package/src/components/__snapshots__/stories.test.tsx.snap +2264 -0
  155. package/src/components/diff-content-view-layout.test.ts +198 -0
  156. package/src/components/diff-content-view-layout.ts +84 -0
  157. package/src/components/editor-grid.test.ts +86 -0
  158. package/src/components/editor-grid.ts +138 -0
  159. package/src/components/file-tree.test.ts +60 -0
  160. package/src/components/file-tree.ts +112 -0
  161. package/src/components/menu-control.stories.tsx +35 -0
  162. package/src/components/menu-control.tsx +43 -0
  163. package/src/components/pages/Workspace.stories.tsx +181 -0
  164. package/src/components/primitives/Button.stories.tsx +32 -0
  165. package/src/components/primitives/Button.tsx +59 -0
  166. package/src/components/primitives/Card.stories.tsx +25 -0
  167. package/src/components/primitives/Card.tsx +63 -0
  168. package/src/components/primitives/Dialog.stories.tsx +24 -0
  169. package/src/components/primitives/Dialog.test.tsx +37 -0
  170. package/src/components/primitives/Dialog.tsx +71 -0
  171. package/src/components/primitives/DialogActions.stories.tsx +20 -0
  172. package/src/components/primitives/DialogActions.tsx +39 -0
  173. package/src/components/primitives/IconButton.stories.tsx +36 -0
  174. package/src/components/primitives/IconButton.tsx +62 -0
  175. package/src/components/primitives/StatusBar.stories.tsx +11 -0
  176. package/src/components/primitives/StatusBar.tsx +23 -0
  177. package/src/components/primitives/Switch.stories.tsx +17 -0
  178. package/src/components/primitives/Switch.tsx +32 -0
  179. package/src/components/primitives/Tabs.stories.tsx +29 -0
  180. package/src/components/primitives/Tabs.tsx +102 -0
  181. package/src/components/primitives/Toolbar.stories.tsx +17 -0
  182. package/src/components/primitives/Toolbar.tsx +14 -0
  183. package/src/components/primitives/Tree.stories.tsx +124 -0
  184. package/src/components/primitives/Tree.tsx +100 -0
  185. package/src/components/primitives/frame.test.ts +12 -0
  186. package/src/components/primitives/frame.ts +17 -0
  187. package/src/components/primitives/icons.ts +61 -0
  188. package/src/components/primitives/tree-model.test.ts +99 -0
  189. package/src/components/primitives/tree-model.ts +137 -0
  190. package/src/components/quick-actions-editor.stories.tsx +58 -0
  191. package/src/components/quick-actions-editor.test.tsx +117 -0
  192. package/src/components/quick-actions-editor.tsx +226 -0
  193. package/src/components/share-dialog-store.ts +58 -0
  194. package/src/components/stories-app.tsx +193 -0
  195. package/src/components/stories.test.tsx +74 -0
  196. package/src/components/story-fixtures.ts +102 -0
  197. package/src/components/story-tree.test.ts +108 -0
  198. package/src/components/story.ts +159 -0
  199. package/src/components/syntax-highlight.ts +133 -0
  200. package/src/components/terminal-pane.test.tsx +40 -0
  201. package/src/components/terminal-pane.ts +199 -0
  202. package/src/components/theme-context.stories.tsx +46 -0
  203. package/src/components/theme-context.tsx +37 -0
  204. package/src/components/thread-tree.test.ts +134 -0
  205. package/src/components/thread-tree.ts +109 -0
  206. package/src/components/truncate-text.ts +7 -0
  207. package/src/components/truncate-title.test.ts +31 -0
  208. package/src/components/truncate-title.ts +15 -0
  209. package/src/config-identity.test.ts +78 -0
  210. package/src/config.test.ts +579 -34
  211. package/src/config.ts +533 -57
  212. package/src/diff-fold.test.ts +78 -0
  213. package/src/diff-fold.ts +126 -0
  214. package/src/diff-hunk-curate.test.ts +315 -0
  215. package/src/diff-hunk-curate.ts +153 -0
  216. package/src/diff-intraline.test.ts +238 -0
  217. package/src/diff-intraline.ts +236 -0
  218. package/src/diff-syntax.test.ts +112 -0
  219. package/src/diff-syntax.ts +179 -0
  220. package/src/discussions.ts +79 -0
  221. package/src/editor.test.ts +149 -0
  222. package/src/editor.ts +139 -9
  223. package/src/ghostty-terminal.test.ts +85 -0
  224. package/src/ghostty-terminal.ts +199 -0
  225. package/src/github-identity.test.ts +58 -0
  226. package/src/github-identity.ts +41 -0
  227. package/src/herdr.test.ts +9 -34
  228. package/src/herdr.ts +25 -21
  229. package/src/index.ts +29 -0
  230. package/src/integrations.test.ts +41 -0
  231. package/src/integrations.ts +32 -0
  232. package/src/intent-dispatch.test.ts +724 -0
  233. package/src/intent-dispatch.ts +690 -0
  234. package/src/key-bindings.test.ts +156 -0
  235. package/src/key-bindings.ts +459 -0
  236. package/src/keymap.test.ts +456 -115
  237. package/src/keymap.ts +315 -81
  238. package/src/kitty-image.test.ts +66 -0
  239. package/src/kitty-image.ts +138 -0
  240. package/src/launch-layout.test.ts +78 -0
  241. package/src/launch-layout.ts +62 -0
  242. package/src/mark-runs.ts +121 -0
  243. package/src/markdown-editor-syntax-style.ts +50 -0
  244. package/src/markdown-grid.test.ts +101 -0
  245. package/src/markdown-grid.ts +203 -0
  246. package/src/markdown-highlight.test.ts +61 -0
  247. package/src/markdown-highlight.ts +122 -0
  248. package/src/pasted-image.test.ts +67 -0
  249. package/src/pasted-image.ts +60 -0
  250. package/src/perf/otlp-trace.test.ts +116 -0
  251. package/src/perf/otlp-trace.ts +130 -0
  252. package/src/perf/perf-report.ts +17 -0
  253. package/src/perf/perf-timings.test.ts +18 -0
  254. package/src/perf/perf-timings.ts +38 -0
  255. package/src/prototype-browser.test.ts +81 -0
  256. package/src/prototype-browser.ts +329 -0
  257. package/src/prototype-pixels.tsx +35 -0
  258. package/src/pty.test.ts +81 -0
  259. package/src/pty.ts +245 -0
  260. package/src/ready-signal.test.ts +85 -0
  261. package/src/ready-signal.ts +52 -0
  262. package/src/run.ts +85 -12
  263. package/src/serve.test.ts +24 -12
  264. package/src/serve.ts +52 -10
  265. package/src/share.ts +237 -0
  266. package/src/skills.test.ts +74 -0
  267. package/src/skills.ts +75 -0
  268. package/src/slash-palette.test.ts +103 -0
  269. package/src/slash-palette.ts +129 -0
  270. package/src/split-diff.test.ts +110 -0
  271. package/src/split-diff.ts +149 -0
  272. package/src/test-support.test.ts +264 -0
  273. package/src/test-support.ts +369 -0
  274. package/src/theme-presets.test.ts +51 -0
  275. package/src/theme-presets.ts +219 -0
  276. package/src/theme.ts +88 -19
  277. package/src/thread-chords.test.ts +79 -0
  278. package/src/thread-chords.ts +163 -0
  279. package/src/thread-code-syntax.ts +45 -0
  280. package/src/thread-controller.curate.test.ts +476 -0
  281. package/src/thread-controller.derivation.test.ts +62 -0
  282. package/src/thread-controller.live-diff.test.ts +322 -0
  283. package/src/thread-controller.optimistic.test.ts +69 -0
  284. package/src/thread-controller.share.test.ts +561 -0
  285. package/src/thread-controller.switch-cache.test.ts +176 -0
  286. package/src/thread-controller.tree.test.ts +261 -0
  287. package/src/thread-controller.ts +2050 -0
  288. package/src/thread-selection.test.ts +210 -0
  289. package/src/thread-selection.ts +243 -0
  290. package/src/tree-view.test.ts +128 -0
  291. package/src/tree-view.ts +153 -0
  292. package/src/use-annotation-surface.tsx +975 -0
  293. package/src/use-changes-workbench.ts +190 -0
  294. package/src/use-frame-measure.ts +41 -0
  295. package/src/use-remember-layout.ts +31 -0
  296. package/src/use-repo-changes.ts +87 -0
  297. package/src/use-settings-dialog.tsx +364 -0
  298. package/src/use-terminal-virtualizer.ts +259 -0
  299. package/src/use-thread-body-editing.tsx +58 -0
  300. package/src/version.ts +5 -0
  301. package/src/view-diff.test.ts +279 -9
  302. package/src/view-diff.ts +460 -24
  303. package/src/view-plan.regression.test.ts +60 -0
  304. package/src/view-plan.test.ts +545 -0
  305. package/src/view-plan.ts +1019 -0
  306. package/src/visual/contrast.test.ts +35 -0
  307. package/src/visual/contrast.ts +33 -0
  308. package/src/walk.test.ts +121 -0
  309. package/src/walk.ts +74 -0
  310. package/src/workbench-snapshot.test.ts +99 -0
  311. package/src/workbench-snapshot.ts +59 -0
  312. package/src/session-controller.ts +0 -398
  313. package/src/syntax.test.ts +0 -41
  314. package/src/syntax.ts +0 -88
  315. package/src/view.test.ts +0 -157
  316. package/src/view.ts +0 -364
@@ -0,0 +1,137 @@
1
+ // The app shell's top bar: left panel toggle + icons + label, an optional title, right icons + panel toggle.
2
+
3
+ import React from "react";
4
+ import { DARK, type Theme } from "../theme";
5
+ import { NERD } from "./primitives/icons";
6
+
7
+ export interface ShellTab {
8
+ label: string;
9
+ active?: boolean;
10
+ }
11
+
12
+ // A header icon; active paints it in the brand accent to mark the current view or mode.
13
+ export interface ShellIcon {
14
+ glyph: string;
15
+ active?: boolean;
16
+ onPress?: () => void;
17
+ }
18
+
19
+ export interface ShellHeaderProps {
20
+ leftIcons?: readonly ShellIcon[];
21
+ leftLabel?: string;
22
+ titleIcon?: string;
23
+ title?: string;
24
+ tabs?: readonly ShellTab[];
25
+ rightIcons?: readonly ShellIcon[];
26
+ sidebarOpen?: boolean;
27
+ onToggleSidebar?: () => void;
28
+ inspectorOpen?: boolean;
29
+ onToggleInspector?: () => void;
30
+ theme?: Theme;
31
+ }
32
+
33
+ function IconView({
34
+ icon,
35
+ tokens,
36
+ marginRight,
37
+ }: {
38
+ icon: ShellIcon;
39
+ tokens: Theme;
40
+ marginRight: number;
41
+ }): React.ReactNode {
42
+ const color = icon.active ? tokens.accent : tokens.textMuted;
43
+
44
+ // alignSelf center keeps the clickable box on the same row as sibling icons
45
+ return (
46
+ <box onMouseUp={icon.onPress} style={{ alignSelf: "center", marginRight }}>
47
+ <text fg={color}>{icon.glyph}</text>
48
+ </box>
49
+ );
50
+ }
51
+
52
+ export function ShellHeader({
53
+ leftIcons = [],
54
+ leftLabel,
55
+ titleIcon,
56
+ title,
57
+ tabs = [],
58
+ rightIcons = [],
59
+ sidebarOpen = true,
60
+ onToggleSidebar,
61
+ inspectorOpen = true,
62
+ onToggleInspector,
63
+ theme,
64
+ }: ShellHeaderProps): React.ReactNode {
65
+ const tokens = theme ?? DARK;
66
+
67
+ return (
68
+ <box
69
+ style={{
70
+ flexDirection: "row",
71
+ height: "100%",
72
+ alignItems: "center",
73
+ paddingLeft: 2,
74
+ paddingRight: 2,
75
+ }}
76
+ >
77
+ <box style={{ flexDirection: "row", alignItems: "center" }}>
78
+ {onToggleSidebar !== undefined ? (
79
+ <IconView
80
+ icon={{
81
+ glyph: sidebarOpen ? NERD.sidebarLeft : NERD.sidebarLeftOff,
82
+ onPress: onToggleSidebar,
83
+ }}
84
+ tokens={tokens}
85
+ marginRight={3}
86
+ />
87
+ ) : null}
88
+ {leftIcons.map((icon, index) => (
89
+ <IconView
90
+ key={`left-${index}-${icon.glyph}`}
91
+ icon={icon}
92
+ tokens={tokens}
93
+ marginRight={3}
94
+ />
95
+ ))}
96
+ {leftLabel !== undefined ? (
97
+ <text fg={tokens.textMuted}>
98
+ {leftLabel}
99
+ {" "}
100
+ </text>
101
+ ) : null}
102
+ {titleIcon !== undefined ? <text fg={tokens.textDim}>{titleIcon} </text> : null}
103
+ {title !== undefined ? <text fg={tokens.text}>{title}</text> : null}
104
+ </box>
105
+ <box style={{ flexGrow: 1 }} />
106
+ <box style={{ flexDirection: "row", alignItems: "center" }}>
107
+ {tabs.map((tab, index) => (
108
+ <text
109
+ key={`tab-${index}-${tab.label}`}
110
+ fg={tab.active ? tokens.accent : tokens.textMuted}
111
+ >
112
+ {tab.label}
113
+ {" "}
114
+ </text>
115
+ ))}
116
+ {rightIcons.map((icon, index) => (
117
+ <IconView
118
+ key={`right-${index}-${icon.glyph}`}
119
+ icon={icon}
120
+ tokens={tokens}
121
+ marginRight={3}
122
+ />
123
+ ))}
124
+ {onToggleInspector !== undefined ? (
125
+ <IconView
126
+ icon={{
127
+ glyph: inspectorOpen ? NERD.sidebarRight : NERD.sidebarRightOff,
128
+ onPress: onToggleInspector,
129
+ }}
130
+ tokens={tokens}
131
+ marginRight={0}
132
+ />
133
+ ) : null}
134
+ </box>
135
+ </box>
136
+ );
137
+ }
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import { DEFAULT_QUICK_ACTIONS } from "../config";
4
+ import type { Story, StoryMeta } from "./story";
5
+ import { SlashComposer } from "./SlashComposer";
6
+
7
+ export const meta: StoryMeta = { title: "Chrome/SlashComposer" };
8
+
9
+ export const Empty: Story = {
10
+ render: () => (
11
+ <box style={{ width: 60, height: 8, paddingLeft: 1, backgroundColor: DARK.panel }}>
12
+ <SlashComposer
13
+ seed=""
14
+ glyph="●"
15
+ quickActions={DEFAULT_QUICK_ACTIONS}
16
+ tokens={DARK}
17
+ onSubmit={() => {}}
18
+ />
19
+ </box>
20
+ ),
21
+ size: { width: 60, height: 8 },
22
+ };
23
+
24
+ export const WithDraft: Story = {
25
+ render: () => (
26
+ <box style={{ width: 60, height: 8, paddingLeft: 1, backgroundColor: DARK.panel }}>
27
+ <SlashComposer
28
+ seed="looks good, ship it"
29
+ glyph="●"
30
+ quickActions={DEFAULT_QUICK_ACTIONS}
31
+ tokens={DARK}
32
+ onSubmit={() => {}}
33
+ />
34
+ </box>
35
+ ),
36
+ size: { width: 60, height: 8 },
37
+ };
@@ -0,0 +1,87 @@
1
+ /** A composer with the "/" actions-and-skills palette, sharing the inline-comment edit experience. */
2
+
3
+ import React, { useContext, useState } from "react";
4
+ import { useKeyboard } from "@opentui/react";
5
+ import type { Theme } from "../theme";
6
+ import type { QuickAction } from "../config";
7
+ import { SlashSkillsContext } from "../skills";
8
+ import {
9
+ activeSlashToken,
10
+ insertSlashItem,
11
+ mergeSlashItems,
12
+ slashFilter,
13
+ slashItemsFrom,
14
+ } from "../slash-palette";
15
+ import { Composer, ComposerPalette } from "./AnnotationCards";
16
+
17
+ export interface SlashComposerProps {
18
+ seed: string;
19
+ glyph: string;
20
+ quickActions: QuickAction[];
21
+ tokens: Theme;
22
+ onSubmit: (text: string) => void;
23
+ onInput?: (text: string) => void;
24
+ placeholder?: string;
25
+ }
26
+
27
+ export function SlashComposer({
28
+ seed,
29
+ glyph,
30
+ quickActions,
31
+ tokens,
32
+ onSubmit,
33
+ onInput,
34
+ placeholder,
35
+ }: SlashComposerProps): React.ReactNode {
36
+ const skills = useContext(SlashSkillsContext);
37
+ const [mountSeed, setMountSeed] = useState(seed);
38
+ const [text, setText] = useState(seed);
39
+ const [caret, setCaret] = useState(seed.length);
40
+ const [slashIndex, setSlashIndex] = useState(0);
41
+
42
+ const paletteItems = mergeSlashItems(slashItemsFrom(quickActions), skills);
43
+ const token = activeSlashToken(text, caret);
44
+ const items = token !== null ? slashFilter(paletteItems, token.slice(1)) : [];
45
+ const slashActive = token !== null && items.length > 0;
46
+
47
+ useKeyboard((key) => {
48
+ if (!slashActive) return;
49
+ const selected = Math.min(slashIndex, items.length - 1);
50
+
51
+ if (key.name === "up") return setSlashIndex(Math.max(0, selected - 1));
52
+ if (key.name === "down") return setSlashIndex(Math.min(items.length - 1, selected + 1));
53
+ if (key.name === "tab" || (key.name === "return" && !key.meta && !key.ctrl && !key.super)) {
54
+ const inserted = insertSlashItem(text, caret, items[selected]!.name);
55
+
56
+ setMountSeed(inserted.text);
57
+ setText(inserted.text);
58
+ setCaret(inserted.caret);
59
+ onInput?.(inserted.text);
60
+ }
61
+ });
62
+
63
+ return (
64
+ <box style={{ flexDirection: "column" }}>
65
+ <Composer
66
+ key={mountSeed}
67
+ seed={mountSeed}
68
+ glyph={glyph}
69
+ tokens={tokens}
70
+ placeholder={placeholder}
71
+ onReady={() => {}}
72
+ onSave={onSubmit}
73
+ onInput={(next, nextCaret) => {
74
+ setText(next);
75
+ setCaret(nextCaret);
76
+ onInput?.(next);
77
+ }}
78
+ />
79
+ <ComposerPalette
80
+ slashActive={slashActive}
81
+ slashItems={items}
82
+ slashIndex={slashIndex}
83
+ tokens={tokens}
84
+ />
85
+ </box>
86
+ );
87
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { ThreadFooter } from "./ThreadFooter";
5
+
6
+ export const meta: StoryMeta = { title: "Chrome/ThreadFooter" };
7
+
8
+ function Frame({ children }: { children: React.ReactNode }): React.ReactNode {
9
+ return <box style={{ width: "100%", backgroundColor: DARK.panel }}>{children}</box>;
10
+ }
11
+
12
+ export const Default: Story = {
13
+ render: () => (
14
+ <Frame>
15
+ <ThreadFooter repo="cueloop" branch="main" onSubmit={() => {}} />
16
+ </Frame>
17
+ ),
18
+ expectedColors: [DARK.accent, DARK.textMuted],
19
+ size: { width: 80, height: 2 },
20
+ };
21
+
22
+ export const ReadOnly: Story = {
23
+ render: () => (
24
+ <Frame>
25
+ <ThreadFooter repo="cueloop" branch="design/tui" canSubmit={false} onSubmit={() => {}} />
26
+ </Frame>
27
+ ),
28
+ expectedColors: [DARK.textDim],
29
+ size: { width: 80, height: 2 },
30
+ };
@@ -0,0 +1,47 @@
1
+ /** The thread footer keeps its repo/branch context on one line: a long branch truncates to an
2
+ * ellipsis rather than wrapping and pushing the send control onto a second row. */
3
+
4
+ import { test, expect } from "bun:test";
5
+ import React from "react";
6
+ import { testRender } from "@opentui/react/test-utils";
7
+ import { ThreadFooter } from "./ThreadFooter";
8
+ import { settle } from "../test-support";
9
+ import { DARK } from "../theme";
10
+
11
+ test("a long branch truncates on one line and keeps the send control in place", async () => {
12
+ const setup = await testRender(
13
+ <box style={{ width: 60, height: 3, backgroundColor: DARK.background }}>
14
+ <box style={{ flexGrow: 1 }} />
15
+ <ThreadFooter
16
+ repo="cueloop"
17
+ branch="design/tui-tree-and-edgy-really-long-name"
18
+ onSubmit={() => {}}
19
+ theme={DARK}
20
+ />
21
+ </box>,
22
+ { width: 60, height: 3 },
23
+ );
24
+ await settle(setup);
25
+ await settle(setup);
26
+
27
+ const frame = setup.captureCharFrame();
28
+ const footerRow = frame.split("\n").find((line) => line.includes("cueloop"))!;
29
+ // the repo/branch context and the send control share the single footer row
30
+ expect(footerRow).toContain("Send message (0)");
31
+ // the branch is clipped to an ellipsis, not wrapped onto a second row
32
+ expect(footerRow).toContain("…");
33
+ expect(frame).not.toContain("long-name");
34
+
35
+ setup.renderer.destroy();
36
+ });
37
+
38
+ test("the footer shows the open annotation count", async () => {
39
+ const setup = await testRender(
40
+ <ThreadFooter repo="cueloop" branch="main" pendingAnnotations={3} theme={DARK} />,
41
+ { width: 60, height: 3 },
42
+ );
43
+
44
+ await settle(setup);
45
+ expect(setup.captureCharFrame()).toContain("Send message (3)");
46
+ setup.renderer.destroy();
47
+ });
@@ -0,0 +1,76 @@
1
+ // The thread's bottom band: a header-like bar under the thread column, or the
2
+ // Changes column when a zoom drops the thread column. It carries the repo/branch
3
+ // context on the left and a written, clickable "Send message (n)" control on the right.
4
+
5
+ import React, { useRef } from "react";
6
+ import type { BoxRenderable } from "@opentui/core";
7
+ import { DARK, type Theme } from "../theme";
8
+ import { useFrameMeasure } from "../use-frame-measure";
9
+ import { truncateTitle } from "./truncate-title";
10
+ import { NERD } from "./primitives/icons";
11
+
12
+ export interface ThreadFooterProps {
13
+ repo: string;
14
+ branch: string;
15
+ onSubmit?: () => void;
16
+ /** When false the send control dims and does not fire (e.g. an observer). */
17
+ canSubmit?: boolean;
18
+ /** Open reviewer annotations included in the next Message. */
19
+ pendingAnnotations?: number;
20
+ theme?: Theme;
21
+ }
22
+
23
+ const ICON_COLUMNS = 2; // folder glyph + its trailing space
24
+ const SEPARATOR = " / ";
25
+
26
+ /** Rows the footer band occupies, so a footer-less sibling pane can offset its centered content to match. */
27
+ export const THREAD_FOOTER_HEIGHT = 2;
28
+
29
+ export function ThreadFooter({
30
+ repo,
31
+ branch,
32
+ onSubmit,
33
+ canSubmit = true,
34
+ pendingAnnotations = 0,
35
+ theme,
36
+ }: ThreadFooterProps): React.ReactNode {
37
+ const tokens = theme ?? DARK;
38
+ const boxRef = useRef<BoxRenderable | null>(null);
39
+ const width = useFrameMeasure(
40
+ () => boxRef.current?.width ?? 0,
41
+ (left, right) => left === right,
42
+ 0,
43
+ );
44
+ const branchBudget = width - ICON_COLUMNS - repo.length - SEPARATOR.length;
45
+ const clippedBranch = width > 0 ? truncateTitle(branch, Math.max(0, branchBudget)) : branch;
46
+
47
+ return (
48
+ <box
49
+ style={{
50
+ flexDirection: "row",
51
+ height: THREAD_FOOTER_HEIGHT,
52
+ alignItems: "center",
53
+ paddingLeft: 1,
54
+ paddingRight: 1,
55
+ backgroundColor: tokens.panel,
56
+ borderStyle: "single",
57
+ border: ["top"],
58
+ borderColor: tokens.border,
59
+ }}
60
+ >
61
+ <box ref={boxRef} style={{ flexShrink: 1, minWidth: 0, marginRight: 2 }}>
62
+ <text wrapMode="none">
63
+ <span fg={tokens.blue}>{`${NERD.folderClosed} `}</span>
64
+ <span fg={tokens.textMuted}>{repo}</span>
65
+ <span fg={tokens.textDim}>{`${SEPARATOR}${clippedBranch}`}</span>
66
+ </text>
67
+ </box>
68
+ <box style={{ flexGrow: 1 }} />
69
+ <box onMouseUp={canSubmit ? onSubmit : undefined} style={{ flexShrink: 0 }}>
70
+ <text fg={canSubmit ? tokens.accent : tokens.textDim}>
71
+ {`Send message (${pendingAnnotations})`}
72
+ </text>
73
+ </box>
74
+ </box>
75
+ );
76
+ }
@@ -0,0 +1,46 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { ThreadTree } from "./ThreadTree";
5
+ import { groupInbox } from "./thread-tree";
6
+ import { fixtureDiffSession, fixturePlanSession } from "./story-fixtures";
7
+
8
+ export const meta: StoryMeta = { title: "Surfaces/ThreadTree" };
9
+
10
+ /** ThreadTree is the body under App's header/menu chrome; a frame gives it height. */
11
+ function BodyFrame({ children }: { children: React.ReactNode }): React.ReactNode {
12
+ return <box style={{ flexDirection: "column", width: "100%", height: "100%" }}>{children}</box>;
13
+ }
14
+
15
+ export const TwoPending: Story = {
16
+ render: () => {
17
+ const plan = fixturePlanSession();
18
+ // one repo-bound thread (a project) and one standalone thread
19
+ const projectPlan = {
20
+ ...plan,
21
+ workspace: {
22
+ ...plan.workspace,
23
+ rootCommit: "root-1",
24
+ remote: "git@github.com:acme/widget.git",
25
+ },
26
+ };
27
+ const { rows } = groupInbox([projectPlan, fixtureDiffSession()]);
28
+
29
+ return (
30
+ <BodyFrame>
31
+ <ThreadTree rows={rows} cursor={0} />
32
+ </BodyFrame>
33
+ );
34
+ },
35
+ expectedColors: [DARK.accent],
36
+ size: { width: 80, height: 12 },
37
+ };
38
+
39
+ export const Empty: Story = {
40
+ render: () => (
41
+ <BodyFrame>
42
+ <ThreadTree rows={[]} cursor={0} />
43
+ </BodyFrame>
44
+ ),
45
+ size: { width: 80, height: 8 },
46
+ };