@cueloop/client 0.1.0-alpha.7 → 0.1.0-alpha.71

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 (248) 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 +39 -32
  8. package/src/App.appearance.test.tsx +53 -0
  9. package/src/App.autoclose.test.tsx +76 -50
  10. package/src/App.diff.test.tsx +156 -42
  11. package/src/App.export.test.tsx +58 -40
  12. package/src/App.plan-review.test.tsx +363 -0
  13. package/src/App.prototype-markdown.test.tsx +92 -0
  14. package/src/App.prototype.test.tsx +176 -0
  15. package/src/App.readonly.test.tsx +128 -67
  16. package/src/App.rendering.test.tsx +61 -35
  17. package/src/App.submit-confirm.test.tsx +191 -0
  18. package/src/App.test.tsx +310 -114
  19. package/src/App.toast.test.tsx +89 -0
  20. package/src/App.tsx +1043 -798
  21. package/src/App.walk.test.tsx +298 -0
  22. package/src/App.workbench.test.tsx +495 -0
  23. package/src/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  24. package/src/annotation-palette.ts +61 -0
  25. package/src/app-key-state.ts +61 -0
  26. package/src/app-screens.tsx +432 -0
  27. package/src/app-view-model.ts +159 -0
  28. package/src/attribution.test.ts +31 -0
  29. package/src/attribution.ts +42 -0
  30. package/src/clipboard.test.ts +23 -0
  31. package/src/clipboard.ts +33 -0
  32. package/src/components/AnnotatableFileView.stories.tsx +42 -0
  33. package/src/components/AnnotatableFileView.test.tsx +102 -0
  34. package/src/components/AnnotatableFileView.tsx +126 -0
  35. package/src/components/AnnotationCards.stories.tsx +87 -0
  36. package/src/components/AnnotationCards.tsx +303 -0
  37. package/src/components/AppHeader.stories.tsx +50 -0
  38. package/src/components/AppHeader.tsx +83 -0
  39. package/src/components/AppShell.stories.tsx +89 -0
  40. package/src/components/AppShell.tsx +290 -0
  41. package/src/components/AppShell.zoom.test.tsx +135 -0
  42. package/src/components/BareWorkbenchFileView.stories.tsx +27 -0
  43. package/src/components/BareWorkbenchFileView.tsx +64 -0
  44. package/src/components/Breadcrumb.stories.tsx +19 -0
  45. package/src/components/Breadcrumb.tsx +45 -0
  46. package/src/components/ChangesColumn.stories.tsx +39 -0
  47. package/src/components/ChangesColumn.tsx +171 -0
  48. package/src/components/CodeBlock.stories.tsx +42 -0
  49. package/src/components/CodeBlock.tsx +84 -0
  50. package/src/components/CompletionOverlay.stories.tsx +23 -0
  51. package/src/components/CompletionOverlay.tsx +60 -0
  52. package/src/components/ConfirmCard.stories.tsx +35 -0
  53. package/src/components/ConfirmCard.tsx +128 -0
  54. package/src/components/ConfirmDialog.stories.tsx +21 -0
  55. package/src/components/ConfirmDialog.tsx +66 -0
  56. package/src/components/DiffContentView.scroll.test.tsx +121 -0
  57. package/src/components/DiffContentView.split.test.tsx +140 -0
  58. package/src/components/DiffContentView.stories.tsx +80 -0
  59. package/src/components/DiffContentView.syntax.test.tsx +82 -0
  60. package/src/components/DiffContentView.tsx +859 -0
  61. package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
  62. package/src/components/DiscussionMarkerRail.tsx +181 -0
  63. package/src/components/EditorGrid.stories.tsx +59 -0
  64. package/src/components/EditorGrid.tabs.test.tsx +118 -0
  65. package/src/components/EditorGrid.tsx +368 -0
  66. package/src/components/EditorGrid.zoom.test.tsx +68 -0
  67. package/src/components/FileContentsView.stories.tsx +16 -0
  68. package/src/components/FileContentsView.test.tsx +29 -0
  69. package/src/components/FileContentsView.tsx +98 -0
  70. package/src/components/KeybindsDialog.stories.tsx +31 -0
  71. package/src/components/KeybindsDialog.tsx +103 -0
  72. package/src/components/MenuBar.stories.tsx +28 -0
  73. package/src/components/MenuBar.tsx +44 -0
  74. package/src/components/OverlayScrollbar.stories.tsx +33 -0
  75. package/src/components/OverlayScrollbar.test.tsx +139 -0
  76. package/src/components/OverlayScrollbar.tsx +129 -0
  77. package/src/components/PaneGrid.stories.tsx +84 -0
  78. package/src/components/PaneGrid.tsx +128 -0
  79. package/src/components/PanelColumn.stories.tsx +25 -0
  80. package/src/components/PanelColumn.tsx +95 -0
  81. package/src/components/ProjectTreeView.stories.tsx +25 -0
  82. package/src/components/ProjectTreeView.test.tsx +32 -0
  83. package/src/components/ProjectTreeView.tsx +83 -0
  84. package/src/components/PromptDialog.stories.tsx +36 -0
  85. package/src/components/PromptDialog.tsx +87 -0
  86. package/src/components/PrototypeContentView.stories.tsx +19 -0
  87. package/src/components/PrototypeContentView.tsx +446 -0
  88. package/src/components/RootOverlay.stories.tsx +44 -0
  89. package/src/components/RootOverlay.tsx +35 -0
  90. package/src/components/ScrollArea.stories.tsx +20 -0
  91. package/src/components/ScrollArea.tsx +44 -0
  92. package/src/components/SettingsDialog.stories.tsx +117 -0
  93. package/src/components/SettingsDialog.tsx +195 -0
  94. package/src/components/SettingsRows.stories.tsx +18 -0
  95. package/src/components/SettingsRows.tsx +112 -0
  96. package/src/components/ShellHeader.stories.tsx +50 -0
  97. package/src/components/ShellHeader.tsx +137 -0
  98. package/src/components/ThreadFooter.stories.tsx +30 -0
  99. package/src/components/ThreadFooter.test.tsx +31 -0
  100. package/src/components/ThreadFooter.tsx +54 -0
  101. package/src/components/ThreadTree.stories.tsx +46 -0
  102. package/src/components/ThreadTree.tsx +219 -0
  103. package/src/components/ThreadView.selection.test.tsx +812 -0
  104. package/src/components/ThreadView.slash.test.tsx +158 -0
  105. package/src/components/ThreadView.stories.tsx +26 -0
  106. package/src/components/ThreadView.tsx +330 -0
  107. package/src/components/ThreadsSidebar.stories.tsx +36 -0
  108. package/src/components/ThreadsSidebar.tsx +71 -0
  109. package/src/components/Toast.stories.tsx +12 -0
  110. package/src/components/Toast.tsx +45 -0
  111. package/src/components/Tooltip.stories.tsx +26 -0
  112. package/src/components/Tooltip.tsx +72 -0
  113. package/src/components/TreePane.stories.tsx +106 -0
  114. package/src/components/TreePane.tsx +157 -0
  115. package/src/components/WalkWizard.stories.tsx +100 -0
  116. package/src/components/WalkWizard.tsx +152 -0
  117. package/src/components/WelcomePlayground.stories.tsx +12 -0
  118. package/src/components/WelcomePlayground.test.tsx +47 -0
  119. package/src/components/WelcomePlayground.tsx +140 -0
  120. package/src/components/__snapshots__/stories.test.tsx.snap +1845 -0
  121. package/src/components/agent-launcher.stories.tsx +26 -0
  122. package/src/components/agent-launcher.test.tsx +50 -0
  123. package/src/components/agent-launcher.tsx +149 -0
  124. package/src/components/diff-content-view-layout.test.ts +198 -0
  125. package/src/components/diff-content-view-layout.ts +84 -0
  126. package/src/components/editor-grid.test.ts +86 -0
  127. package/src/components/editor-grid.ts +138 -0
  128. package/src/components/fade-title.test.ts +49 -0
  129. package/src/components/fade-title.ts +61 -0
  130. package/src/components/file-tree.test.ts +60 -0
  131. package/src/components/file-tree.ts +112 -0
  132. package/src/components/pages/Workspace.stories.tsx +181 -0
  133. package/src/components/primitives/Button.stories.tsx +32 -0
  134. package/src/components/primitives/Button.tsx +52 -0
  135. package/src/components/primitives/Card.stories.tsx +25 -0
  136. package/src/components/primitives/Card.tsx +60 -0
  137. package/src/components/primitives/Dialog.stories.tsx +24 -0
  138. package/src/components/primitives/Dialog.tsx +66 -0
  139. package/src/components/primitives/IconButton.stories.tsx +36 -0
  140. package/src/components/primitives/IconButton.tsx +62 -0
  141. package/src/components/primitives/StatusBar.stories.tsx +11 -0
  142. package/src/components/primitives/StatusBar.tsx +23 -0
  143. package/src/components/primitives/Tabs.stories.tsx +29 -0
  144. package/src/components/primitives/Tabs.tsx +102 -0
  145. package/src/components/primitives/Toolbar.stories.tsx +17 -0
  146. package/src/components/primitives/Toolbar.tsx +14 -0
  147. package/src/components/primitives/Tree.stories.tsx +124 -0
  148. package/src/components/primitives/Tree.tsx +96 -0
  149. package/src/components/primitives/frame.test.ts +12 -0
  150. package/src/components/primitives/frame.ts +14 -0
  151. package/src/components/primitives/icons.ts +61 -0
  152. package/src/components/primitives/tree-model.test.ts +99 -0
  153. package/src/components/primitives/tree-model.ts +137 -0
  154. package/src/components/quick-actions-editor.stories.tsx +54 -0
  155. package/src/components/quick-actions-editor.test.tsx +49 -0
  156. package/src/components/quick-actions-editor.tsx +150 -0
  157. package/src/components/session-tree.test.ts +134 -0
  158. package/src/components/session-tree.ts +108 -0
  159. package/src/components/stories-app.tsx +184 -0
  160. package/src/components/stories.test.tsx +66 -0
  161. package/src/components/story-fixtures.ts +102 -0
  162. package/src/components/story-tree.test.ts +100 -0
  163. package/src/components/story.ts +142 -0
  164. package/src/components/syntax-highlight.ts +110 -0
  165. package/src/components/terminal-pane.test.tsx +40 -0
  166. package/src/components/terminal-pane.ts +199 -0
  167. package/src/components/theme-context.stories.tsx +46 -0
  168. package/src/components/theme-context.tsx +37 -0
  169. package/src/components/truncate-text.ts +7 -0
  170. package/src/config.test.ts +461 -31
  171. package/src/config.ts +389 -57
  172. package/src/diff-fold.test.ts +78 -0
  173. package/src/diff-fold.ts +126 -0
  174. package/src/diff-hunk-curate.test.ts +315 -0
  175. package/src/diff-hunk-curate.ts +153 -0
  176. package/src/diff-intraline.test.ts +165 -0
  177. package/src/diff-intraline.ts +146 -0
  178. package/src/diff-syntax.test.ts +112 -0
  179. package/src/diff-syntax.ts +179 -0
  180. package/src/discussions.ts +79 -0
  181. package/src/editor.test.ts +149 -0
  182. package/src/editor.ts +139 -9
  183. package/src/ghostty-terminal.test.ts +85 -0
  184. package/src/ghostty-terminal.ts +199 -0
  185. package/src/herdr.test.ts +9 -34
  186. package/src/herdr.ts +25 -21
  187. package/src/index.ts +11 -0
  188. package/src/integrations.test.ts +41 -0
  189. package/src/integrations.ts +32 -0
  190. package/src/intent-dispatch.test.ts +698 -0
  191. package/src/intent-dispatch.ts +700 -0
  192. package/src/key-bindings.test.ts +156 -0
  193. package/src/key-bindings.ts +465 -0
  194. package/src/keymap.test.ts +611 -0
  195. package/src/keymap.ts +410 -0
  196. package/src/kitty-image.test.ts +66 -0
  197. package/src/kitty-image.ts +138 -0
  198. package/src/mark-runs.ts +120 -0
  199. package/src/prototype-browser.test.ts +56 -0
  200. package/src/prototype-browser.ts +307 -0
  201. package/src/prototype-pixels.tsx +35 -0
  202. package/src/pty.test.ts +81 -0
  203. package/src/pty.ts +245 -0
  204. package/src/ready-signal.test.ts +85 -0
  205. package/src/ready-signal.ts +52 -0
  206. package/src/run.ts +30 -9
  207. package/src/serve.test.ts +24 -12
  208. package/src/serve.ts +20 -9
  209. package/src/share.ts +215 -0
  210. package/src/skills.test.ts +74 -0
  211. package/src/skills.ts +68 -0
  212. package/src/slash-palette.test.ts +54 -0
  213. package/src/slash-palette.ts +104 -0
  214. package/src/split-diff.test.ts +110 -0
  215. package/src/split-diff.ts +149 -0
  216. package/src/test-support.test.ts +264 -0
  217. package/src/test-support.ts +355 -0
  218. package/src/theme-presets.test.ts +51 -0
  219. package/src/theme-presets.ts +214 -0
  220. package/src/theme.ts +84 -19
  221. package/src/thread-chords.test.ts +170 -0
  222. package/src/thread-chords.ts +169 -0
  223. package/src/thread-controller.curate.test.ts +438 -0
  224. package/src/thread-controller.live-diff.test.ts +199 -0
  225. package/src/thread-controller.share.test.ts +472 -0
  226. package/src/thread-controller.tree.test.ts +255 -0
  227. package/src/thread-controller.ts +1509 -0
  228. package/src/thread-selection.test.ts +210 -0
  229. package/src/thread-selection.ts +243 -0
  230. package/src/tree-view.test.ts +128 -0
  231. package/src/tree-view.ts +148 -0
  232. package/src/use-annotation-surface.tsx +851 -0
  233. package/src/use-changes-workbench.ts +153 -0
  234. package/src/use-frame-measure.ts +38 -0
  235. package/src/use-settings-dialog.tsx +274 -0
  236. package/src/use-terminal-virtualizer.ts +233 -0
  237. package/src/version.ts +5 -0
  238. package/src/view-diff.test.ts +66 -9
  239. package/src/view-diff.ts +231 -23
  240. package/src/view-plan.regression.test.ts +60 -0
  241. package/src/view-plan.test.ts +406 -0
  242. package/src/view-plan.ts +570 -0
  243. package/src/walk.test.ts +121 -0
  244. package/src/walk.ts +74 -0
  245. package/src/syntax.test.ts +0 -41
  246. package/src/syntax.ts +0 -88
  247. package/src/view.test.ts +0 -157
  248. package/src/view.ts +0 -312
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { PanelColumn, FileTab } from "./PanelColumn";
5
+
6
+ export const meta: StoryMeta = { title: "Surfaces/PanelColumn" };
7
+
8
+ export const WithFileTab: Story = {
9
+ render: () => (
10
+ <box style={{ width: "100%", height: "100%", flexDirection: "row" }}>
11
+ <PanelColumn
12
+ width={40}
13
+ border="left"
14
+ header={<FileTab label="Welcome" active onClose={() => {}} theme={DARK} />}
15
+ theme={DARK}
16
+ >
17
+ <box style={{ paddingLeft: 1, paddingTop: 1 }}>
18
+ <text fg={DARK.textDim}>panel body</text>
19
+ </box>
20
+ </PanelColumn>
21
+ </box>
22
+ ),
23
+ expectedColors: [DARK.accent, DARK.border],
24
+ size: { width: 50, height: 8 },
25
+ };
@@ -0,0 +1,95 @@
1
+ // A workbench panel: one full-height grid column with a file-tab header cell on
2
+ // top and a body below. The header row carries the tabs on the left and optional
3
+ // controls on the right, underlined by a thin rule in the same divider gray so the
4
+ // header bottoms join the side rules into one grid. The side border runs from the top.
5
+
6
+ import React, { useState } from "react";
7
+ import type { Theme } from "../theme";
8
+ import { NERD, HEADER_UNDERLINE_CHARS } from "./primitives/icons";
9
+
10
+ export interface FileTabProps {
11
+ label: string;
12
+ active?: boolean;
13
+ /** When set, a close control reveals on hover (a disposable tab). */
14
+ onClose?: () => void;
15
+ theme?: Theme;
16
+ }
17
+
18
+ /** A file tab: the label in the accent when active, with a hover-revealed close. */
19
+ export function FileTab({ label, active, onClose, theme }: FileTabProps): React.ReactNode {
20
+ const [hovered, setHovered] = useState(false);
21
+
22
+ return (
23
+ <box
24
+ style={{ flexDirection: "row", paddingRight: 1 }}
25
+ onMouseOver={() => setHovered(true)}
26
+ onMouseOut={() => setHovered(false)}
27
+ >
28
+ <text fg={active ? theme?.accent : theme?.textDim}>{label}</text>
29
+ {onClose ? (
30
+ <box onMouseUp={onClose} style={{ paddingLeft: 1 }}>
31
+ <text fg={theme?.textDim}>{hovered ? NERD.close : " "}</text>
32
+ </box>
33
+ ) : null}
34
+ </box>
35
+ );
36
+ }
37
+
38
+ export interface PanelColumnProps {
39
+ /** Fixed column width; omit for the flex-growing center panel. */
40
+ width?: number;
41
+ /** Draw the divider on this side (the sidebar borders right, the rest border left). */
42
+ border?: "left" | "right";
43
+ /** Tabs and controls for the header cell. */
44
+ header: React.ReactNode;
45
+ /** Right-aligned header controls (toggles, search). */
46
+ headerRight?: React.ReactNode;
47
+ children: React.ReactNode;
48
+ theme?: Theme;
49
+ }
50
+
51
+ export function PanelColumn({
52
+ width,
53
+ border,
54
+ header,
55
+ headerRight,
56
+ children,
57
+ theme,
58
+ }: PanelColumnProps): React.ReactNode {
59
+ return (
60
+ <box
61
+ style={{
62
+ width,
63
+ // grow panes split evenly and clip wide content (a long diff line) rather than stretching
64
+ flexGrow: width === undefined ? 1 : undefined,
65
+ flexBasis: width === undefined ? 0 : undefined,
66
+ minWidth: 0,
67
+ flexDirection: "column",
68
+ borderStyle: "single",
69
+ border: border ? [border] : [],
70
+ borderColor: theme?.border,
71
+ }}
72
+ >
73
+ <box
74
+ customBorderChars={HEADER_UNDERLINE_CHARS}
75
+ style={{
76
+ flexDirection: "row",
77
+ height: 2,
78
+ paddingLeft: 1,
79
+ // right controls need the same breathing room as the left, or the rightmost icon renders
80
+ // squeezed against the terminal's last column
81
+ paddingRight: 1,
82
+ backgroundColor: theme?.panel,
83
+ borderStyle: "single",
84
+ border: ["bottom"],
85
+ borderColor: theme?.border,
86
+ }}
87
+ >
88
+ {header}
89
+ <box style={{ flexGrow: 1 }} />
90
+ {headerRight}
91
+ </box>
92
+ <box style={{ flexGrow: 1, flexDirection: "column" }}>{children}</box>
93
+ </box>
94
+ );
95
+ }
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { ProjectTreeView } from "./ProjectTreeView";
5
+
6
+ export const meta: StoryMeta = { title: "Surfaces/ProjectTreeView" };
7
+
8
+ const SAMPLE = [
9
+ "packages/client/src/App.tsx",
10
+ "packages/client/src/components/AppShell.tsx",
11
+ "packages/daemon/src/api.ts",
12
+ "README.md",
13
+ ];
14
+
15
+ export const Loaded: Story = {
16
+ render: () => (
17
+ <ProjectTreeView
18
+ loadFiles={() => Promise.resolve(SAMPLE)}
19
+ onSelectFile={() => {}}
20
+ theme={DARK}
21
+ />
22
+ ),
23
+ expectedColors: [DARK.textDim],
24
+ size: { width: 40, height: 16 },
25
+ };
@@ -0,0 +1,32 @@
1
+ import { expect, test } from "bun:test";
2
+ import React from "react";
3
+ import { testRender } from "@opentui/react/test-utils";
4
+ import { ProjectTreeView } from "./ProjectTreeView";
5
+ import { clickText, waitForText } from "../test-support";
6
+ import { DARK } from "../theme";
7
+
8
+ test("renders the loaded files as a tree and opens a file on click", async () => {
9
+ const opened: string[] = [];
10
+ const setup = await testRender(
11
+ <ProjectTreeView
12
+ loadFiles={() => Promise.resolve(["src/App.tsx", "README.md"])}
13
+ onSelectFile={(path) => opened.push(path)}
14
+ theme={DARK}
15
+ />,
16
+ { width: 40, height: 16 },
17
+ );
18
+
19
+ await waitForText(setup, "README.md");
20
+ await clickText(setup, "README.md");
21
+
22
+ expect(opened).toContain("README.md");
23
+ });
24
+
25
+ test("shows an empty hint when there are no files", async () => {
26
+ const setup = await testRender(
27
+ <ProjectTreeView loadFiles={() => Promise.resolve([])} onSelectFile={() => {}} theme={DARK} />,
28
+ { width: 40, height: 8 },
29
+ );
30
+
31
+ await waitForText(setup, "empty");
32
+ });
@@ -0,0 +1,83 @@
1
+ // The Project pane in tree mode: the workspace's tracked files as a collapsible directory tree.
2
+ // Folders start collapsed and expand on click; a file click opens it as a tab. The caller keys this
3
+ // on the session id so switching sessions remounts it and never shows the previous repository's paths.
4
+
5
+ import { ScrollArea } from "./ScrollArea";
6
+ import React, { useEffect, useMemo, useRef, useState } from "react";
7
+ import type { Theme } from "../theme";
8
+ import { useComponentTheme } from "./theme-context";
9
+ import { Tree } from "./primitives/Tree";
10
+ import { buildPathTree } from "./file-tree";
11
+
12
+ export interface ProjectTreeViewProps {
13
+ loadFiles: () => Promise<string[]>;
14
+ onSelectFile: (path: string) => void;
15
+ theme?: Theme;
16
+ }
17
+
18
+ export function ProjectTreeView({
19
+ loadFiles,
20
+ onSelectFile,
21
+ theme,
22
+ }: ProjectTreeViewProps): React.ReactNode {
23
+ const tokens = useComponentTheme(theme);
24
+ const [paths, setPaths] = useState<readonly string[] | null>(null);
25
+ const [expandedIds, setExpandedIds] = useState<ReadonlySet<string>>(new Set());
26
+ const nodes = useMemo(() => (paths ? buildPathTree(paths) : []), [paths]);
27
+ const loadRef = useRef(loadFiles);
28
+ useEffect(() => {
29
+ loadRef.current = loadFiles;
30
+ });
31
+
32
+ useEffect(() => {
33
+ let alive = true;
34
+ void loadRef.current().then(
35
+ (files) => {
36
+ if (alive) setPaths(files);
37
+ },
38
+ () => {
39
+ if (alive) setPaths([]);
40
+ },
41
+ );
42
+
43
+ return () => {
44
+ alive = false;
45
+ };
46
+ }, []);
47
+
48
+ if (paths === null) {
49
+ return (
50
+ <box style={{ flexGrow: 1, paddingLeft: 1, paddingTop: 1 }}>
51
+ <text fg={tokens.textDim}>loading...</text>
52
+ </box>
53
+ );
54
+ }
55
+ if (paths.length === 0) {
56
+ return (
57
+ <box style={{ flexGrow: 1, paddingLeft: 1, paddingTop: 1 }}>
58
+ <text fg={tokens.textDim}>empty</text>
59
+ </box>
60
+ );
61
+ }
62
+
63
+ return (
64
+ <ScrollArea>
65
+ <Tree
66
+ nodes={nodes}
67
+ expandedIds={expandedIds}
68
+ onSelect={onSelectFile}
69
+ onToggle={(id) =>
70
+ setExpandedIds((current) => {
71
+ const next = new Set(current);
72
+
73
+ if (next.has(id)) next.delete(id);
74
+ else next.add(id);
75
+
76
+ return next;
77
+ })
78
+ }
79
+ theme={theme}
80
+ />
81
+ </ScrollArea>
82
+ );
83
+ }
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { PromptDialog } from "./PromptDialog";
5
+
6
+ export const meta: StoryMeta = { title: "Overlays/PromptDialog" };
7
+
8
+ export const RenameAuthor: Story = {
9
+ render: () => (
10
+ <PromptDialog
11
+ isOpen
12
+ title=" Rename author "
13
+ label="Display name for this collaborator:"
14
+ value="Robin"
15
+ placeholder="their name"
16
+ onInput={() => {}}
17
+ />
18
+ ),
19
+ expectedColors: [DARK.elevated, DARK.accent],
20
+ size: { width: 64, height: 16 },
21
+ };
22
+
23
+ export const NameYourself: Story = {
24
+ render: () => (
25
+ <PromptDialog
26
+ isOpen
27
+ title=" Welcome "
28
+ label="Your name (optional) - it attributes the notes you leave:"
29
+ value=""
30
+ placeholder="your name"
31
+ onInput={() => {}}
32
+ />
33
+ ),
34
+ expectedColors: [DARK.elevated],
35
+ size: { width: 64, height: 16 },
36
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * A centered single-field text prompt on the shared Dialog overlay. The focused
3
+ * input owns typing/cursor natively; the reducer's `prompt` overlay drives ⏎
4
+ * save / esc cancel (like the composer). Used to rename an author and to ask a
5
+ * collaborator for their name.
6
+ */
7
+
8
+ import React, { useEffect, useRef } from "react";
9
+ import { useTerminalDimensions } from "@opentui/react";
10
+ import type { KeyBinding, TextareaRenderable } from "@opentui/core";
11
+ import type { Theme } from "../theme";
12
+ import { useComponentTheme } from "./theme-context";
13
+ import { Dialog } from "./primitives/Dialog";
14
+
15
+ // ⏎ submits (suppresses the textarea's default newline); the grammar owns save.
16
+ const PROMPT_KEY_BINDINGS: KeyBinding[] = [{ name: "return", action: "submit" }];
17
+
18
+ export interface PromptDialogProps {
19
+ isOpen: boolean;
20
+ title?: string;
21
+ label: string;
22
+ value: string;
23
+ placeholder?: string;
24
+ onInput: (text: string) => void;
25
+ theme?: Theme;
26
+ }
27
+
28
+ export function PromptDialog({
29
+ isOpen,
30
+ title,
31
+ label,
32
+ value,
33
+ placeholder,
34
+ onInput,
35
+ theme,
36
+ }: PromptDialogProps): React.ReactNode {
37
+ const tokens = useComponentTheme(theme);
38
+ const { width: terminalWidth } = useTerminalDimensions();
39
+ const inputRef = useRef<TextareaRenderable | null>(null);
40
+
41
+ useEffect(() => {
42
+ // open with the caret after the seeded value, like a text field
43
+ if (inputRef.current) inputRef.current.cursorOffset = value.length;
44
+ // eslint-disable-next-line react-hooks/exhaustive-deps
45
+ }, []);
46
+ if (!isOpen) return null;
47
+
48
+ return (
49
+ <Dialog
50
+ isOpen
51
+ title={title}
52
+ width={Math.min(54, terminalWidth - 6)}
53
+ height={7}
54
+ background={tokens.elevated}
55
+ theme={theme}
56
+ >
57
+ <box
58
+ style={{
59
+ flexDirection: "column",
60
+ flexGrow: 1,
61
+ paddingLeft: 1,
62
+ paddingRight: 1,
63
+ paddingTop: 1,
64
+ }}
65
+ >
66
+ <text fg={tokens.textDim}>{label}</text>
67
+ <textarea
68
+ ref={inputRef}
69
+ focused
70
+ initialValue={value}
71
+ placeholder={placeholder}
72
+ keyBindings={PROMPT_KEY_BINDINGS}
73
+ onContentChange={() => onInput(inputRef.current?.plainText ?? "")}
74
+ style={{
75
+ height: 1,
76
+ backgroundColor: tokens.elevated,
77
+ focusedBackgroundColor: tokens.elevated,
78
+ textColor: tokens.text,
79
+ focusedTextColor: tokens.text,
80
+ }}
81
+ />
82
+ <box style={{ flexGrow: 1 }} />
83
+ <text fg={tokens.textDim}>enter save · esc cancel</text>
84
+ </box>
85
+ </Dialog>
86
+ );
87
+ }
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { PrototypeContentView } from "./PrototypeContentView";
5
+
6
+ export const meta: StoryMeta = { title: "Surfaces/PrototypeContentView" };
7
+
8
+ // The story renderer reports no kitty graphics, so the view shows its fallback.
9
+ export const Unsupported: Story = {
10
+ render: () => (
11
+ <PrototypeContentView
12
+ prototypePath="/tmp/example.html"
13
+ canComment={true}
14
+ onCommentElement={() => {}}
15
+ />
16
+ ),
17
+ expectedColors: [DARK.textDim],
18
+ size: { width: 80, height: 24 },
19
+ };