@cueloop/client 0.1.0-alpha.8 → 0.1.0-alpha.80

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 (307) 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 +40 -32
  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 +176 -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 +428 -0
  16. package/src/App.prototype-markdown.test.tsx +92 -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 +222 -0
  23. package/src/App.test.tsx +333 -131
  24. package/src/App.toast.test.tsx +98 -0
  25. package/src/App.tsx +1338 -608
  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 +701 -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 +574 -0
  35. package/src/app-view-model.ts +162 -0
  36. package/src/attribution.test.ts +31 -0
  37. package/src/attribution.ts +42 -0
  38. package/src/clipboard.test.ts +23 -0
  39. package/src/clipboard.ts +33 -0
  40. package/src/components/AnnotatableFileView.stories.tsx +42 -0
  41. package/src/components/AnnotatableFileView.test.tsx +102 -0
  42. package/src/components/AnnotatableFileView.tsx +134 -0
  43. package/src/components/AnnotationCards.stories.tsx +86 -0
  44. package/src/components/AnnotationCards.tsx +341 -0
  45. package/src/components/AnsiScreen.stories.tsx +12 -0
  46. package/src/components/AnsiScreen.tsx +28 -0
  47. package/src/components/AppHeader.stories.tsx +50 -0
  48. package/src/components/AppHeader.tsx +83 -0
  49. package/src/components/AppShell.brand.test.tsx +58 -0
  50. package/src/components/AppShell.stories.tsx +89 -0
  51. package/src/components/AppShell.tsx +329 -0
  52. package/src/components/AppShell.zoom.test.tsx +135 -0
  53. package/src/components/BareWorkbenchFileView.stories.tsx +27 -0
  54. package/src/components/BareWorkbenchFileView.tsx +64 -0
  55. package/src/components/Breadcrumb.stories.tsx +19 -0
  56. package/src/components/Breadcrumb.tsx +45 -0
  57. package/src/components/ChangesColumn.stories.tsx +44 -0
  58. package/src/components/ChangesColumn.tsx +199 -0
  59. package/src/components/ChangesFileTree.test.tsx +61 -0
  60. package/src/components/CodeBlock.stories.tsx +42 -0
  61. package/src/components/CodeBlock.tsx +84 -0
  62. package/src/components/CompletionOverlay.stories.tsx +37 -0
  63. package/src/components/CompletionOverlay.tsx +77 -0
  64. package/src/components/ConfirmCard.stories.tsx +37 -0
  65. package/src/components/ConfirmCard.tsx +153 -0
  66. package/src/components/ConfirmDialog.stories.tsx +21 -0
  67. package/src/components/ConfirmDialog.tsx +72 -0
  68. package/src/components/DiffContentView.scroll.test.tsx +121 -0
  69. package/src/components/DiffContentView.split.test.tsx +140 -0
  70. package/src/components/DiffContentView.stories.tsx +80 -0
  71. package/src/components/DiffContentView.syntax.test.tsx +82 -0
  72. package/src/components/DiffContentView.tsx +876 -0
  73. package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
  74. package/src/components/DiscussionMarkerRail.tsx +186 -0
  75. package/src/components/EditorGrid.stories.tsx +59 -0
  76. package/src/components/EditorGrid.tabs.test.tsx +118 -0
  77. package/src/components/EditorGrid.tsx +426 -0
  78. package/src/components/EditorGrid.zoom.test.tsx +68 -0
  79. package/src/components/FileContentsView.stories.tsx +16 -0
  80. package/src/components/FileContentsView.test.tsx +29 -0
  81. package/src/components/FileContentsView.tsx +98 -0
  82. package/src/components/GridTabContent.stories.tsx +59 -0
  83. package/src/components/GridTabContent.tsx +182 -0
  84. package/src/components/HandleChipsInput.stories.tsx +24 -0
  85. package/src/components/HandleChipsInput.test.tsx +35 -0
  86. package/src/components/HandleChipsInput.tsx +145 -0
  87. package/src/components/KeybindsDialog.stories.tsx +31 -0
  88. package/src/components/KeybindsDialog.tsx +103 -0
  89. package/src/components/MarkdownGridBlock.stories.tsx +33 -0
  90. package/src/components/MarkdownGridBlock.tsx +72 -0
  91. package/src/components/MarkdownThreadEditor.stories.tsx +15 -0
  92. package/src/components/MarkdownThreadEditor.test.tsx +98 -0
  93. package/src/components/MarkdownThreadEditor.tsx +162 -0
  94. package/src/components/MenuBar.stories.tsx +28 -0
  95. package/src/components/MenuBar.tsx +44 -0
  96. package/src/components/NavModeHint.stories.tsx +40 -0
  97. package/src/components/NavModeHint.tsx +31 -0
  98. package/src/components/OverlayScrollbar.stories.tsx +33 -0
  99. package/src/components/OverlayScrollbar.test.tsx +139 -0
  100. package/src/components/OverlayScrollbar.tsx +129 -0
  101. package/src/components/PaneGrid.stories.tsx +84 -0
  102. package/src/components/PaneGrid.tsx +128 -0
  103. package/src/components/PanelColumn.stories.tsx +25 -0
  104. package/src/components/PanelColumn.tsx +98 -0
  105. package/src/components/ProjectTreeView.stories.tsx +25 -0
  106. package/src/components/ProjectTreeView.test.tsx +59 -0
  107. package/src/components/ProjectTreeView.tsx +111 -0
  108. package/src/components/PromptDialog.stories.tsx +36 -0
  109. package/src/components/PromptDialog.test.tsx +61 -0
  110. package/src/components/PromptDialog.tsx +99 -0
  111. package/src/components/PrototypeContentView.stories.tsx +19 -0
  112. package/src/components/PrototypeContentView.tsx +446 -0
  113. package/src/components/RootOverlay.stories.tsx +45 -0
  114. package/src/components/RootOverlay.tsx +48 -0
  115. package/src/components/ScrollArea.stories.tsx +20 -0
  116. package/src/components/ScrollArea.tsx +44 -0
  117. package/src/components/SettingsDialog.stories.tsx +117 -0
  118. package/src/components/SettingsDialog.tsx +186 -0
  119. package/src/components/SettingsRows.stories.tsx +18 -0
  120. package/src/components/SettingsRows.tsx +112 -0
  121. package/src/components/ShareDialog.stories.tsx +121 -0
  122. package/src/components/ShareDialog.tsx +552 -0
  123. package/src/components/ShellHeader.stories.tsx +50 -0
  124. package/src/components/ShellHeader.tsx +137 -0
  125. package/src/components/SlashComposer.stories.tsx +37 -0
  126. package/src/components/SlashComposer.tsx +87 -0
  127. package/src/components/ThreadFooter.stories.tsx +30 -0
  128. package/src/components/ThreadFooter.test.tsx +47 -0
  129. package/src/components/ThreadFooter.tsx +76 -0
  130. package/src/components/ThreadTree.stories.tsx +46 -0
  131. package/src/components/ThreadTree.tsx +255 -0
  132. package/src/components/ThreadView.selection.test.tsx +899 -0
  133. package/src/components/ThreadView.slash.test.tsx +146 -0
  134. package/src/components/ThreadView.stories.tsx +26 -0
  135. package/src/components/ThreadView.tsx +472 -0
  136. package/src/components/Toast.stories.tsx +19 -0
  137. package/src/components/Toast.tsx +52 -0
  138. package/src/components/Tooltip.stories.tsx +26 -0
  139. package/src/components/Tooltip.tsx +72 -0
  140. package/src/components/TreePane.stories.tsx +106 -0
  141. package/src/components/TreePane.tsx +157 -0
  142. package/src/components/WalkWizard.stories.tsx +100 -0
  143. package/src/components/WalkWizard.tsx +150 -0
  144. package/src/components/WelcomePlayground.stories.tsx +12 -0
  145. package/src/components/WelcomePlayground.test.tsx +47 -0
  146. package/src/components/WelcomePlayground.tsx +142 -0
  147. package/src/components/__snapshots__/stories.test.tsx.snap +2238 -0
  148. package/src/components/diff-content-view-layout.test.ts +198 -0
  149. package/src/components/diff-content-view-layout.ts +84 -0
  150. package/src/components/editor-grid.test.ts +86 -0
  151. package/src/components/editor-grid.ts +138 -0
  152. package/src/components/file-tree.test.ts +60 -0
  153. package/src/components/file-tree.ts +112 -0
  154. package/src/components/menu-control.stories.tsx +35 -0
  155. package/src/components/menu-control.tsx +43 -0
  156. package/src/components/pages/Workspace.stories.tsx +181 -0
  157. package/src/components/primitives/Button.stories.tsx +32 -0
  158. package/src/components/primitives/Button.tsx +56 -0
  159. package/src/components/primitives/Card.stories.tsx +25 -0
  160. package/src/components/primitives/Card.tsx +63 -0
  161. package/src/components/primitives/Dialog.stories.tsx +24 -0
  162. package/src/components/primitives/Dialog.test.tsx +37 -0
  163. package/src/components/primitives/Dialog.tsx +71 -0
  164. package/src/components/primitives/DialogActions.stories.tsx +20 -0
  165. package/src/components/primitives/DialogActions.tsx +39 -0
  166. package/src/components/primitives/IconButton.stories.tsx +36 -0
  167. package/src/components/primitives/IconButton.tsx +62 -0
  168. package/src/components/primitives/StatusBar.stories.tsx +11 -0
  169. package/src/components/primitives/StatusBar.tsx +23 -0
  170. package/src/components/primitives/Switch.stories.tsx +17 -0
  171. package/src/components/primitives/Switch.tsx +32 -0
  172. package/src/components/primitives/Tabs.stories.tsx +29 -0
  173. package/src/components/primitives/Tabs.tsx +102 -0
  174. package/src/components/primitives/Toolbar.stories.tsx +17 -0
  175. package/src/components/primitives/Toolbar.tsx +14 -0
  176. package/src/components/primitives/Tree.stories.tsx +124 -0
  177. package/src/components/primitives/Tree.tsx +100 -0
  178. package/src/components/primitives/frame.test.ts +12 -0
  179. package/src/components/primitives/frame.ts +17 -0
  180. package/src/components/primitives/icons.ts +61 -0
  181. package/src/components/primitives/tree-model.test.ts +99 -0
  182. package/src/components/primitives/tree-model.ts +137 -0
  183. package/src/components/quick-actions-editor.stories.tsx +58 -0
  184. package/src/components/quick-actions-editor.test.tsx +117 -0
  185. package/src/components/quick-actions-editor.tsx +226 -0
  186. package/src/components/share-dialog-store.ts +58 -0
  187. package/src/components/stories-app.tsx +193 -0
  188. package/src/components/stories.test.tsx +74 -0
  189. package/src/components/story-fixtures.ts +102 -0
  190. package/src/components/story-tree.test.ts +108 -0
  191. package/src/components/story.ts +159 -0
  192. package/src/components/syntax-highlight.ts +133 -0
  193. package/src/components/terminal-pane.test.tsx +40 -0
  194. package/src/components/terminal-pane.ts +199 -0
  195. package/src/components/theme-context.stories.tsx +46 -0
  196. package/src/components/theme-context.tsx +37 -0
  197. package/src/components/thread-tree.test.ts +134 -0
  198. package/src/components/thread-tree.ts +109 -0
  199. package/src/components/truncate-text.ts +7 -0
  200. package/src/components/truncate-title.test.ts +31 -0
  201. package/src/components/truncate-title.ts +15 -0
  202. package/src/config-identity.test.ts +78 -0
  203. package/src/config.test.ts +517 -33
  204. package/src/config.ts +467 -57
  205. package/src/diff-fold.test.ts +78 -0
  206. package/src/diff-fold.ts +126 -0
  207. package/src/diff-hunk-curate.test.ts +315 -0
  208. package/src/diff-hunk-curate.ts +153 -0
  209. package/src/diff-intraline.test.ts +238 -0
  210. package/src/diff-intraline.ts +236 -0
  211. package/src/diff-syntax.test.ts +112 -0
  212. package/src/diff-syntax.ts +179 -0
  213. package/src/discussions.ts +79 -0
  214. package/src/editor.test.ts +149 -0
  215. package/src/editor.ts +139 -9
  216. package/src/ghostty-terminal.test.ts +85 -0
  217. package/src/ghostty-terminal.ts +199 -0
  218. package/src/github-identity.test.ts +58 -0
  219. package/src/github-identity.ts +41 -0
  220. package/src/herdr.test.ts +9 -34
  221. package/src/herdr.ts +25 -21
  222. package/src/index.ts +13 -0
  223. package/src/integrations.test.ts +41 -0
  224. package/src/integrations.ts +32 -0
  225. package/src/intent-dispatch.test.ts +723 -0
  226. package/src/intent-dispatch.ts +692 -0
  227. package/src/key-bindings.test.ts +156 -0
  228. package/src/key-bindings.ts +459 -0
  229. package/src/keymap.test.ts +461 -113
  230. package/src/keymap.ts +321 -79
  231. package/src/kitty-image.test.ts +66 -0
  232. package/src/kitty-image.ts +138 -0
  233. package/src/launch-layout.test.ts +52 -0
  234. package/src/launch-layout.ts +42 -0
  235. package/src/mark-runs.ts +121 -0
  236. package/src/markdown-editor-syntax-style.ts +50 -0
  237. package/src/markdown-grid.test.ts +101 -0
  238. package/src/markdown-grid.ts +203 -0
  239. package/src/markdown-highlight.test.ts +61 -0
  240. package/src/markdown-highlight.ts +122 -0
  241. package/src/pasted-image.test.ts +67 -0
  242. package/src/pasted-image.ts +60 -0
  243. package/src/perf/otlp-trace.test.ts +116 -0
  244. package/src/perf/otlp-trace.ts +130 -0
  245. package/src/perf/perf-report.ts +17 -0
  246. package/src/perf/perf-timings.test.ts +18 -0
  247. package/src/perf/perf-timings.ts +38 -0
  248. package/src/prototype-browser.test.ts +64 -0
  249. package/src/prototype-browser.ts +329 -0
  250. package/src/prototype-pixels.tsx +35 -0
  251. package/src/pty.test.ts +81 -0
  252. package/src/pty.ts +245 -0
  253. package/src/ready-signal.test.ts +85 -0
  254. package/src/ready-signal.ts +52 -0
  255. package/src/run.ts +78 -13
  256. package/src/serve.test.ts +24 -12
  257. package/src/serve.ts +52 -10
  258. package/src/share.ts +237 -0
  259. package/src/skills.test.ts +74 -0
  260. package/src/skills.ts +75 -0
  261. package/src/slash-palette.test.ts +103 -0
  262. package/src/slash-palette.ts +129 -0
  263. package/src/split-diff.test.ts +110 -0
  264. package/src/split-diff.ts +149 -0
  265. package/src/test-support.test.ts +264 -0
  266. package/src/test-support.ts +369 -0
  267. package/src/theme-presets.test.ts +51 -0
  268. package/src/theme-presets.ts +214 -0
  269. package/src/theme.ts +84 -19
  270. package/src/thread-chords.test.ts +79 -0
  271. package/src/thread-chords.ts +163 -0
  272. package/src/thread-controller.curate.test.ts +450 -0
  273. package/src/thread-controller.derivation.test.ts +62 -0
  274. package/src/thread-controller.live-diff.test.ts +322 -0
  275. package/src/thread-controller.optimistic.test.ts +69 -0
  276. package/src/thread-controller.share.test.ts +561 -0
  277. package/src/thread-controller.switch-cache.test.ts +176 -0
  278. package/src/thread-controller.tree.test.ts +261 -0
  279. package/src/thread-controller.ts +1893 -0
  280. package/src/thread-selection.test.ts +210 -0
  281. package/src/thread-selection.ts +243 -0
  282. package/src/tree-view.test.ts +128 -0
  283. package/src/tree-view.ts +148 -0
  284. package/src/use-annotation-surface.tsx +915 -0
  285. package/src/use-changes-workbench.ts +190 -0
  286. package/src/use-frame-measure.ts +41 -0
  287. package/src/use-remember-layout.ts +31 -0
  288. package/src/use-settings-dialog.tsx +320 -0
  289. package/src/use-terminal-virtualizer.ts +233 -0
  290. package/src/use-thread-body-editing.tsx +58 -0
  291. package/src/version.ts +5 -0
  292. package/src/view-diff.test.ts +137 -9
  293. package/src/view-diff.ts +269 -23
  294. package/src/view-plan.regression.test.ts +60 -0
  295. package/src/view-plan.test.ts +456 -0
  296. package/src/view-plan.ts +719 -0
  297. package/src/visual/contrast.test.ts +35 -0
  298. package/src/visual/contrast.ts +33 -0
  299. package/src/walk.test.ts +121 -0
  300. package/src/walk.ts +74 -0
  301. package/src/workbench-snapshot.test.ts +81 -0
  302. package/src/workbench-snapshot.ts +29 -0
  303. package/src/session-controller.ts +0 -345
  304. package/src/syntax.test.ts +0 -41
  305. package/src/syntax.ts +0 -88
  306. package/src/view.test.ts +0 -157
  307. package/src/view.ts +0 -312
@@ -0,0 +1,48 @@
1
+ // A root overlay slot: OpenTUI has no z-index, so an overlay drawn inside a pane is sliced by the
2
+ // next pane's border. Anything pushed here renders after every pane (last in draw order), so it sits
3
+ // on top of the whole shell - the same trick the tooltip uses. Callers position their node with
4
+ // absolute screen coordinates and hold a stable key, so several overlays (a menu, a hover preview)
5
+ // coexist instead of clobbering one shared slot.
6
+
7
+ import React, { createContext, useContext, useMemo, useState } from "react";
8
+
9
+ interface RootOverlayApi {
10
+ setOverlay: (key: string, node: React.ReactNode) => void;
11
+ clearOverlay: (key: string) => void;
12
+ }
13
+
14
+ const RootOverlayContext = createContext<RootOverlayApi>({
15
+ setOverlay: () => {},
16
+ clearOverlay: () => {},
17
+ });
18
+
19
+ export function useRootOverlay(): RootOverlayApi {
20
+ return useContext(RootOverlayContext);
21
+ }
22
+
23
+ export function RootOverlayProvider({ children }: { children: React.ReactNode }): React.ReactNode {
24
+ const [overlays, setOverlays] = useState<Map<string, React.ReactNode>>(new Map());
25
+ const api = useMemo<RootOverlayApi>(
26
+ () => ({
27
+ setOverlay: (key, node) => setOverlays((current) => new Map(current).set(key, node)),
28
+ clearOverlay: (key) =>
29
+ setOverlays((current) => {
30
+ if (!current.has(key)) return current;
31
+ const next = new Map(current);
32
+ next.delete(key);
33
+
34
+ return next;
35
+ }),
36
+ }),
37
+ [],
38
+ );
39
+
40
+ return (
41
+ <RootOverlayContext.Provider value={api}>
42
+ {children}
43
+ {[...overlays].map(([key, node]) => (
44
+ <React.Fragment key={key}>{node}</React.Fragment>
45
+ ))}
46
+ </RootOverlayContext.Provider>
47
+ );
48
+ }
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import { ScrollArea } from "./ScrollArea";
4
+ import type { Story, StoryMeta } from "./story";
5
+
6
+ export const meta: StoryMeta = { title: "Chrome/ScrollArea" };
7
+
8
+ // Taller-than-viewport content; the overlay bar stays hidden until the surface scrolls.
9
+ export const AtRest: Story = {
10
+ render: () => (
11
+ <box style={{ width: 24, height: 8, backgroundColor: DARK.background }}>
12
+ <ScrollArea>
13
+ {Array.from({ length: 24 }, (_unused, index) => (
14
+ <text key={index} fg={DARK.textMuted}>{`row ${index + 1}`}</text>
15
+ ))}
16
+ </ScrollArea>
17
+ </box>
18
+ ),
19
+ size: { width: 24, height: 8 },
20
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A scrollbox whose bar follows the global rule: no always-on scrollbar. The built-in bar is
3
+ * hidden and an OverlayScrollbar rides the rightmost column, appearing only while scrolling.
4
+ * Pass scrollRef to keep scrollTo / scrollChildIntoView on the underlying ScrollBox.
5
+ */
6
+
7
+ import React, { useRef, type ReactNode, type RefObject } from "react";
8
+ import type { ScrollBoxRenderable } from "@opentui/core";
9
+ import type { Theme } from "../theme";
10
+ import { OverlayScrollbar } from "./OverlayScrollbar";
11
+
12
+ export interface ScrollAreaProps {
13
+ children: ReactNode;
14
+ focused?: boolean;
15
+ theme?: Theme;
16
+ scrollRef?: RefObject<ScrollBoxRenderable | null>;
17
+ }
18
+
19
+ export function ScrollArea({
20
+ children,
21
+ focused = false,
22
+ theme,
23
+ scrollRef,
24
+ }: ScrollAreaProps): React.ReactNode {
25
+ const innerRef = useRef<ScrollBoxRenderable | null>(null);
26
+ const attach = (node: ScrollBoxRenderable | null): void => {
27
+ innerRef.current = node;
28
+ if (scrollRef) scrollRef.current = node;
29
+ };
30
+
31
+ return (
32
+ <box style={{ flexGrow: 1, flexDirection: "row" }}>
33
+ <scrollbox
34
+ ref={attach}
35
+ style={{ flexGrow: 1 }}
36
+ focused={focused}
37
+ verticalScrollbarOptions={{ visible: false }}
38
+ >
39
+ {children}
40
+ </scrollbox>
41
+ <OverlayScrollbar scrollbox={innerRef} theme={theme} />
42
+ </box>
43
+ );
44
+ }
@@ -0,0 +1,117 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import { THEME_PRESETS } from "../theme-presets";
4
+ import type { Story, StoryMeta } from "./story";
5
+ import { SettingsDialog, type SettingsCategory } from "./SettingsDialog";
6
+
7
+ export const meta: StoryMeta = { title: "Overlays/SettingsDialog" };
8
+
9
+ const CATEGORIES: SettingsCategory[] = [
10
+ {
11
+ id: "general",
12
+ name: "General",
13
+ rows: [
14
+ { key: "displayName", label: "Display name", kind: "text" },
15
+ {
16
+ key: "autoClose",
17
+ label: "Auto-close on submit",
18
+ kind: "cycle",
19
+ options: ["off", "3s", "on"],
20
+ },
21
+ ],
22
+ },
23
+ {
24
+ id: "display",
25
+ name: "Display",
26
+ rows: [
27
+ {
28
+ key: "planWidth",
29
+ label: "Plan width",
30
+ kind: "cycle",
31
+ options: ["default", "wide", "full"],
32
+ },
33
+ { key: "showLineNumbers", label: "Line numbers", kind: "toggle" },
34
+ ],
35
+ },
36
+ {
37
+ id: "appearance",
38
+ name: "Appearance",
39
+ rows: [
40
+ {
41
+ key: "theme",
42
+ label: "Theme",
43
+ kind: "cycle",
44
+ options: ["cueloop", "Rosé Pine Moon", "Nord"],
45
+ },
46
+ ],
47
+ },
48
+ ];
49
+
50
+ const VALUES = {
51
+ displayName: "amber-heron",
52
+ autoClose: "3s",
53
+ planWidth: "default",
54
+ showLineNumbers: true,
55
+ theme: "Rosé Pine Moon",
56
+ };
57
+
58
+ export const NavZone: Story = {
59
+ render: () => (
60
+ <SettingsDialog
61
+ isOpen
62
+ version="0.1.0-alpha.32"
63
+ keybindsSections={[]}
64
+ categories={CATEGORIES}
65
+ values={VALUES}
66
+ activeCategoryId="general"
67
+ activeRowIndex={0}
68
+ activeZone="nav"
69
+ onCategorySelect={() => {}}
70
+ onRowActivate={() => {}}
71
+ onClose={() => {}}
72
+ />
73
+ ),
74
+ expectedColors: [DARK.accent],
75
+ size: { width: 100, height: 30 },
76
+ };
77
+
78
+ export const BodyZone: Story = {
79
+ render: () => (
80
+ <SettingsDialog
81
+ isOpen
82
+ version="0.1.0-alpha.32"
83
+ keybindsSections={[]}
84
+ categories={CATEGORIES}
85
+ values={VALUES}
86
+ activeCategoryId="display"
87
+ activeRowIndex={1}
88
+ activeZone="body"
89
+ onCategorySelect={() => {}}
90
+ onRowActivate={() => {}}
91
+ onClose={() => {}}
92
+ />
93
+ ),
94
+ expectedColors: [DARK.green],
95
+ size: { width: 100, height: 30 },
96
+ };
97
+
98
+ export const AppearanceThemed: Story = {
99
+ render: () => (
100
+ <SettingsDialog
101
+ isOpen
102
+ version="0.1.0-alpha.32"
103
+ keybindsSections={[]}
104
+ categories={CATEGORIES}
105
+ values={VALUES}
106
+ activeCategoryId="appearance"
107
+ activeRowIndex={0}
108
+ activeZone="body"
109
+ onCategorySelect={() => {}}
110
+ onRowActivate={() => {}}
111
+ onClose={() => {}}
112
+ theme={THEME_PRESETS["rose-pine-moon"]}
113
+ />
114
+ ),
115
+ expectedColors: [THEME_PRESETS["rose-pine-moon"].accent],
116
+ size: { width: 100, height: 30 },
117
+ };
@@ -0,0 +1,186 @@
1
+ /**
2
+ * The settings overlay: a centered Dialog whose left nav lists the categories as
3
+ * first-level leaves (General, Appearance, Actions, Keybinds), and whose right
4
+ * body shows the active category's typed rows or the keybinds cheatsheet. The
5
+ * component is controlled: the caller owns the open flag, the active
6
+ * category/row/zone, and value changes, so the app's keyboard grammar (and the
7
+ * stories catalog) drive it without a second key handler inside.
8
+ */
9
+
10
+ import { ScrollArea } from "./ScrollArea";
11
+ import React from "react";
12
+ import { useTerminalDimensions } from "@opentui/react";
13
+ import type { Theme } from "../theme";
14
+ import type { CheatsheetSection } from "../key-bindings";
15
+ import { useComponentTheme } from "./theme-context";
16
+ import { Dialog } from "./primitives/Dialog";
17
+ import { Tree } from "./primitives/Tree";
18
+ import type { TreeNode } from "./primitives/tree-model";
19
+ import { CycleRow, TextRow, ToggleRow, type SettingsRowDescriptor } from "./SettingsRows";
20
+
21
+ export interface SettingsCategory {
22
+ id: string;
23
+ name: string;
24
+ rows: SettingsRowDescriptor[];
25
+ /** A bespoke body (e.g. the quick-actions editor) rendered instead of typed rows. */
26
+ customBody?: React.ReactNode;
27
+ }
28
+
29
+ export type SettingsValues = Record<string, string | boolean>;
30
+
31
+ /** The category id whose right pane shows the keybinds cheatsheet rather than rows. */
32
+ const KEYBINDS_CATEGORY_ID = "keybinds";
33
+
34
+ export interface SettingsDialogProps {
35
+ isOpen: boolean;
36
+ /** The client version, shown in the dialog footer (its home now the menu bar is gone). */
37
+ version: string;
38
+ categories: SettingsCategory[];
39
+ values: SettingsValues;
40
+ /** The keybinds cheatsheet, shown when the Keybinds leaf is active. */
41
+ keybindsSections: CheatsheetSection[];
42
+ activeCategoryId: string;
43
+ activeRowIndex: number;
44
+ activeZone: "nav" | "body";
45
+ onCategorySelect: (categoryId: string) => void;
46
+ onRowActivate: (row: SettingsRowDescriptor) => void;
47
+ onClose: () => void;
48
+ theme?: Theme;
49
+ }
50
+
51
+ function navTree(categories: SettingsCategory[]): TreeNode[] {
52
+ return categories.map((category) => ({ id: category.id, label: category.name }));
53
+ }
54
+
55
+ function KeybindsBody({
56
+ sections,
57
+ tokens,
58
+ }: {
59
+ sections: CheatsheetSection[];
60
+ tokens: Theme;
61
+ }): React.ReactNode {
62
+ return (
63
+ <ScrollArea>
64
+ {sections.map((section) => (
65
+ <box key={section.title} style={{ flexDirection: "column" }}>
66
+ <text fg={tokens.accent}>{section.title}</text>
67
+ {section.entries.map((entry, index) => (
68
+ <text key={`${section.title}-${index}`}>
69
+ <span fg={tokens.text}>{entry.keys.padEnd(12)}</span>
70
+ <span fg={tokens.textMuted}>{entry.label}</span>
71
+ </text>
72
+ ))}
73
+ <text> </text>
74
+ </box>
75
+ ))}
76
+ </ScrollArea>
77
+ );
78
+ }
79
+
80
+ export function SettingsDialog({
81
+ isOpen,
82
+ version,
83
+ categories,
84
+ values,
85
+ keybindsSections,
86
+ activeCategoryId,
87
+ activeRowIndex,
88
+ activeZone,
89
+ onCategorySelect,
90
+ onRowActivate,
91
+ onClose,
92
+ theme,
93
+ }: SettingsDialogProps): React.ReactNode {
94
+ const tokens = useComponentTheme(theme);
95
+ const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
96
+
97
+ if (!isOpen) return null;
98
+ const category =
99
+ categories.find((candidate) => candidate.id === activeCategoryId) ?? categories[0];
100
+
101
+ if (!category) return null;
102
+ const onKeybinds = category.id === KEYBINDS_CATEGORY_ID;
103
+
104
+ return (
105
+ <Dialog
106
+ isOpen
107
+ title=" Settings "
108
+ width={Math.min(76, terminalWidth - 6)}
109
+ height={Math.min(22, terminalHeight - 4)}
110
+ background={tokens.elevated}
111
+ onDismiss={onClose}
112
+ theme={theme}
113
+ >
114
+ <box style={{ flexDirection: "row", flexGrow: 1 }}>
115
+ <box style={{ flexDirection: "column", width: 20, paddingLeft: 1, paddingRight: 1 }}>
116
+ <Tree
117
+ nodes={navTree(categories)}
118
+ expandedIds={new Set<string>()}
119
+ selectedId={category.id}
120
+ hideIcons
121
+ selectedBackground={tokens.border}
122
+ onSelect={onCategorySelect}
123
+ onToggle={onCategorySelect}
124
+ theme={theme}
125
+ />
126
+ </box>
127
+ <box style={{ borderStyle: "single", border: ["left"], borderColor: tokens.border }} />
128
+ <box style={{ flexDirection: "column", flexGrow: 1, paddingLeft: 2, paddingRight: 1 }}>
129
+ {onKeybinds ? <KeybindsBody sections={keybindsSections} tokens={tokens} /> : null}
130
+ {onKeybinds ? null : category.customBody}
131
+ {onKeybinds
132
+ ? null
133
+ : category.rows.map((row, rowIndex) => {
134
+ const isActive = activeZone === "body" && rowIndex === activeRowIndex;
135
+ const value = values[row.key];
136
+
137
+ if (row.kind === "toggle") {
138
+ return (
139
+ <ToggleRow
140
+ key={row.key}
141
+ label={row.label}
142
+ value={value === true}
143
+ isActive={isActive}
144
+ onPress={() => onRowActivate(row)}
145
+ theme={theme}
146
+ />
147
+ );
148
+ }
149
+ if (row.kind === "cycle") {
150
+ return (
151
+ <CycleRow
152
+ key={row.key}
153
+ label={row.label}
154
+ value={String(value ?? "")}
155
+ isActive={isActive}
156
+ onPress={() => onRowActivate(row)}
157
+ theme={theme}
158
+ />
159
+ );
160
+ }
161
+
162
+ return (
163
+ <TextRow
164
+ key={row.key}
165
+ label={row.label}
166
+ value={String(value ?? "")}
167
+ isActive={isActive}
168
+ onPress={() => onRowActivate(row)}
169
+ theme={theme}
170
+ />
171
+ );
172
+ })}
173
+ </box>
174
+ </box>
175
+ <box style={{ flexDirection: "row", height: 1, paddingLeft: 1, paddingRight: 1 }}>
176
+ <text fg={tokens.textDim}>
177
+ {activeZone === "nav"
178
+ ? "j/k nav · l/tab open · esc close"
179
+ : "j/k row · l/space change · h back · esc close"}
180
+ </text>
181
+ <box style={{ flexGrow: 1 }} />
182
+ <text fg={tokens.textDim}>{`cueloop v${version}`}</text>
183
+ </box>
184
+ </Dialog>
185
+ );
186
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { CycleRow, TextRow, ToggleRow } from "./SettingsRows";
5
+
6
+ export const meta: StoryMeta = { title: "Chrome/SettingsRows" };
7
+
8
+ export const AllKinds: Story = {
9
+ render: () => (
10
+ <box style={{ flexDirection: "column", width: 50 }}>
11
+ <ToggleRow label="Line numbers" value={true} isActive={false} onPress={() => {}} />
12
+ <ToggleRow label="Hide whitespace" value={false} isActive={true} onPress={() => {}} />
13
+ <CycleRow label="Palette" value="dark" isActive={false} onPress={() => {}} />
14
+ <TextRow label="Vault path" value="~/vaults/notes" isActive={false} onPress={() => {}} />
15
+ </box>
16
+ ),
17
+ expectedColors: [DARK.green, DARK.accent, DARK.border],
18
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Typed settings rows for the settings dialog: toggle (on/off), cycle
3
+ * (enumerated options), and text (freeform value). Every row is one line -
4
+ * label column left, value right - and presses go through `onPress` so mouse
5
+ * and keyboard activation share one path.
6
+ */
7
+
8
+ import React from "react";
9
+ import type { Theme } from "../theme";
10
+ import { useComponentTheme } from "./theme-context";
11
+
12
+ export interface SettingsRowDescriptor {
13
+ key: string;
14
+ label: string;
15
+ kind: "toggle" | "cycle" | "text";
16
+ options?: string[];
17
+ hint?: string;
18
+ }
19
+
20
+ interface RowShellProps {
21
+ label: string;
22
+ isActive: boolean;
23
+ onPress: () => void;
24
+ theme?: Theme;
25
+ children: React.ReactNode;
26
+ }
27
+
28
+ function RowShell({ label, isActive, onPress, theme, children }: RowShellProps): React.ReactNode {
29
+ const tokens = useComponentTheme(theme);
30
+
31
+ return (
32
+ <box
33
+ style={{
34
+ flexDirection: "row",
35
+ height: 1,
36
+ backgroundColor: isActive ? tokens.border : undefined,
37
+ }}
38
+ onMouseUp={onPress}
39
+ >
40
+ <box style={{ width: 26, paddingLeft: 1 }}>
41
+ <text fg={isActive ? tokens.text : tokens.textMuted}>{label}</text>
42
+ </box>
43
+ {children}
44
+ </box>
45
+ );
46
+ }
47
+
48
+ export interface ToggleRowProps {
49
+ label: string;
50
+ value: boolean;
51
+ isActive: boolean;
52
+ onPress: () => void;
53
+ theme?: Theme;
54
+ }
55
+
56
+ export function ToggleRow({
57
+ label,
58
+ value,
59
+ isActive,
60
+ onPress,
61
+ theme,
62
+ }: ToggleRowProps): React.ReactNode {
63
+ const tokens = useComponentTheme(theme);
64
+
65
+ return (
66
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
67
+ <text fg={value ? tokens.green : tokens.accent}>{value ? "on" : "off"}</text>
68
+ </RowShell>
69
+ );
70
+ }
71
+
72
+ export interface CycleRowProps {
73
+ label: string;
74
+ value: string;
75
+ isActive: boolean;
76
+ onPress: () => void;
77
+ theme?: Theme;
78
+ }
79
+
80
+ export function CycleRow({
81
+ label,
82
+ value,
83
+ isActive,
84
+ onPress,
85
+ theme,
86
+ }: CycleRowProps): React.ReactNode {
87
+ const tokens = useComponentTheme(theme);
88
+
89
+ return (
90
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
91
+ <text fg={tokens.accent}>{value}</text>
92
+ </RowShell>
93
+ );
94
+ }
95
+
96
+ export interface TextRowProps {
97
+ label: string;
98
+ value: string;
99
+ isActive: boolean;
100
+ onPress: () => void;
101
+ theme?: Theme;
102
+ }
103
+
104
+ export function TextRow({ label, value, isActive, onPress, theme }: TextRowProps): React.ReactNode {
105
+ const tokens = useComponentTheme(theme);
106
+
107
+ return (
108
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
109
+ <text fg={tokens.accent}>{value}</text>
110
+ </RowShell>
111
+ );
112
+ }
@@ -0,0 +1,121 @@
1
+ import React from "react";
2
+ import type { ShareLink } from "@cueloop/schema";
3
+ import { DARK } from "../theme";
4
+ import type { Story, StoryMeta } from "./story";
5
+ import { ShareDialog } from "./ShareDialog";
6
+ import { newWizardDraft, shareDialogStore } from "./share-dialog-store";
7
+
8
+ export const meta: StoryMeta = { title: "Dialogs/Share" };
9
+
10
+ const LINKS: ShareLink[] = [
11
+ { id: "p_ab12cd", name: "reviewers", requireAuth: true, allowlist: ["octocat"] },
12
+ { id: "p_ef34gh", requireAuth: false, allowlist: [] },
13
+ ];
14
+
15
+ const NOOP = {
16
+ onCreateLink: () => {},
17
+ onUpdateLink: () => {},
18
+ onDeleteLink: () => {},
19
+ onCopyLink: () => {},
20
+ onClose: () => {},
21
+ };
22
+
23
+ const SIZE = { width: 80, height: 24 };
24
+
25
+ export const EmptyList: Story = {
26
+ render: () => {
27
+ shareDialogStore.getState().reset();
28
+
29
+ return (
30
+ <ShareDialog
31
+ isOpen
32
+ threadName="Improve retry logic"
33
+ links={[]}
34
+ isOwner
35
+ {...NOOP}
36
+ theme={DARK}
37
+ />
38
+ );
39
+ },
40
+ size: SIZE,
41
+ };
42
+
43
+ export const WithLinks: Story = {
44
+ render: () => {
45
+ shareDialogStore.getState().reset();
46
+
47
+ return (
48
+ <ShareDialog
49
+ isOpen
50
+ threadName="Improve retry logic"
51
+ links={LINKS}
52
+ isOwner
53
+ {...NOOP}
54
+ theme={DARK}
55
+ />
56
+ );
57
+ },
58
+ size: SIZE,
59
+ };
60
+
61
+ export const ExportComingSoon: Story = {
62
+ render: () => {
63
+ shareDialogStore.getState().reset();
64
+ shareDialogStore.getState().setCategory("export");
65
+
66
+ return (
67
+ <ShareDialog
68
+ isOpen
69
+ threadName="Improve retry logic"
70
+ links={LINKS}
71
+ isOwner
72
+ {...NOOP}
73
+ theme={DARK}
74
+ />
75
+ );
76
+ },
77
+ size: SIZE,
78
+ };
79
+
80
+ export const NewLinkWizard: Story = {
81
+ render: () => {
82
+ shareDialogStore.getState().reset();
83
+ shareDialogStore.getState().openWizard(newWizardDraft("Improve retry logic"));
84
+
85
+ return (
86
+ <ShareDialog
87
+ isOpen
88
+ threadName="Improve retry logic"
89
+ links={[]}
90
+ isOwner
91
+ {...NOOP}
92
+ theme={DARK}
93
+ />
94
+ );
95
+ },
96
+ size: SIZE,
97
+ };
98
+
99
+ export const AuthWizard: Story = {
100
+ render: () => {
101
+ shareDialogStore.getState().reset();
102
+ shareDialogStore.getState().openWizard({
103
+ editingId: null,
104
+ name: "reviewers",
105
+ requireAuth: true,
106
+ allowlist: ["octocat", "hubot"],
107
+ });
108
+
109
+ return (
110
+ <ShareDialog
111
+ isOpen
112
+ threadName="Improve retry logic"
113
+ links={[]}
114
+ isOwner
115
+ {...NOOP}
116
+ theme={DARK}
117
+ />
118
+ );
119
+ },
120
+ size: SIZE,
121
+ };