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

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 +1039 -799
  21. package/src/App.walk.test.tsx +298 -0
  22. package/src/App.workbench.test.tsx +461 -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 +286 -0
  41. package/src/components/AppShell.zoom.test.tsx +94 -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 +151 -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,19 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { Breadcrumb } from "./Breadcrumb";
5
+
6
+ export const meta: StoryMeta = { title: "Chrome/Breadcrumb" };
7
+
8
+ export const SessionHeader: Story = {
9
+ render: () => (
10
+ <Breadcrumb
11
+ items={[
12
+ { label: "cueloop", tone: "accent" },
13
+ { label: "Migration Plan · rev 2", tone: "dim" },
14
+ { label: "resolved: approve", tone: "green" },
15
+ ]}
16
+ />
17
+ ),
18
+ expectedColors: [DARK.accent, DARK.green],
19
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * One-row breadcrumb trail: toned segments joined by " · " on the panel
3
+ * surface. The app header is a breadcrumb (product · artifact · revision)
4
+ * followed by state badges; each item picks a semantic tone.
5
+ */
6
+
7
+ import React from "react";
8
+ import type { Theme } from "../theme";
9
+ import { useComponentTheme } from "./theme-context";
10
+
11
+ export type BreadcrumbTone = "accent" | "dim" | "text" | "green";
12
+
13
+ export interface BreadcrumbItem {
14
+ label: string;
15
+ tone?: BreadcrumbTone;
16
+ }
17
+
18
+ export interface BreadcrumbProps {
19
+ items: BreadcrumbItem[];
20
+ theme?: Theme;
21
+ }
22
+
23
+ export function Breadcrumb({ items, theme }: BreadcrumbProps): React.ReactNode {
24
+ const tokens = useComponentTheme(theme);
25
+ const toneColor = (tone?: BreadcrumbTone): string =>
26
+ tone === "accent"
27
+ ? tokens.accent
28
+ : tone === "green"
29
+ ? tokens.green
30
+ : tone === "text"
31
+ ? tokens.text
32
+ : tokens.textDim;
33
+
34
+ return (
35
+ <box style={{ height: 1, backgroundColor: tokens.panel, paddingLeft: 1, flexDirection: "row" }}>
36
+ <text fg={tokens.text}>
37
+ {items.map((item, index) => (
38
+ <span key={index} fg={toneColor(item.tone)}>
39
+ {index === 0 ? item.label : ` · ${item.label}`}
40
+ </span>
41
+ ))}
42
+ </text>
43
+ </box>
44
+ );
45
+ }
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import type { DiffFileContents } from "@cueloop/schema";
3
+ import { DARK } from "../theme";
4
+ import type { Story, StoryMeta } from "./story";
5
+ import { ChangesColumn } from "./ChangesColumn";
6
+
7
+ export const meta: StoryMeta = { title: "Surfaces/ChangesColumn" };
8
+
9
+ function Frame({ children }: { children: React.ReactNode }): React.ReactNode {
10
+ return <box style={{ width: "100%", height: "100%", flexDirection: "row" }}>{children}</box>;
11
+ }
12
+
13
+ const files: DiffFileContents[] = [
14
+ { path: "packages/client/src/App.tsx", oldContents: "a", newContents: "b", status: "modified" },
15
+ {
16
+ path: "packages/client/src/components/ChangesColumn.tsx",
17
+ oldContents: "",
18
+ newContents: "c",
19
+ status: "added",
20
+ },
21
+ { path: "packages/daemon/src/review.ts", oldContents: "d", newContents: "", status: "deleted" },
22
+ { path: "README.md", oldContents: "e", newContents: "f", status: "modified" },
23
+ ];
24
+
25
+ export const Changed: Story = {
26
+ render: () => (
27
+ <Frame>
28
+ <ChangesColumn
29
+ open
30
+ files={files}
31
+ selectedPath="packages/client/src/App.tsx"
32
+ onSelectFile={() => {}}
33
+ theme={DARK}
34
+ />
35
+ </Frame>
36
+ ),
37
+ expectedColors: [DARK.accent, DARK.border],
38
+ size: { width: 60, height: 12 },
39
+ };
@@ -0,0 +1,171 @@
1
+ // The right-hand Changes column: the diff's changed files as a directory tree.
2
+ // A click on a file scrolls the diff to that file. Renders nothing when collapsed
3
+ // so the diff reclaims the width. Mirrors the left Threads column on the far side.
4
+ // Co-located here: the state hook that opens it by context and the header toggle.
5
+
6
+ import { ScrollArea } from "./ScrollArea";
7
+ import React, { useMemo, useState } from "react";
8
+ import type { DiffFileContents, Thread } from "@cueloop/schema";
9
+ import type { Theme } from "../theme";
10
+ import type { DiffRow } from "../view-diff";
11
+ import { Tree } from "./primitives/Tree";
12
+ import { allFolderIds } from "./primitives/tree-model";
13
+ import { IconButton } from "./primitives/IconButton";
14
+ import { NERD } from "./primitives/icons";
15
+ import { buildFileTree } from "./file-tree";
16
+
17
+ export interface DiffColumnsState {
18
+ changesOpen: boolean;
19
+ toggleChanges: () => void;
20
+ /** The path under the diff cursor, highlighted in the Changes tree. */
21
+ currentFilePath?: string;
22
+ scrollToFile: (path: string) => void;
23
+ }
24
+
25
+ /**
26
+ * Owns the right Changes column for a diff: context opens it when you enter a
27
+ * diff thread, and a file click scrolls the sheet to that file's first row.
28
+ * Resets on session change via the guarded render-time pattern, no effect.
29
+ */
30
+ export function useDiffColumns(params: {
31
+ session: Thread | null;
32
+ rows: DiffRow[];
33
+ cursor: number;
34
+ setCursor: (index: number) => void;
35
+ }): DiffColumnsState {
36
+ const { session, rows, cursor, setCursor } = params;
37
+ const contextDefault = session?.artifact.type === "diff";
38
+ const [changesOpen, setChangesOpen] = useState(contextDefault);
39
+ const [seenSessionId, setSeenSessionId] = useState(session?.id);
40
+
41
+ // entering another thread lets context decide again; within a thread the manual toggle stays
42
+ if (session?.id !== seenSessionId) {
43
+ setSeenSessionId(session?.id);
44
+ setChangesOpen(contextDefault);
45
+ }
46
+
47
+ return {
48
+ changesOpen,
49
+ toggleChanges: () => setChangesOpen((open) => !open),
50
+ currentFilePath: rows[cursor]?.file,
51
+ scrollToFile: (path: string) => {
52
+ const fileRowIndex = rows.findIndex((row) => row.kind === "file" && row.file === path);
53
+
54
+ if (fileRowIndex >= 0) setCursor(fileRowIndex);
55
+ },
56
+ };
57
+ }
58
+
59
+ /** The header toggle for the Changes column; shown only for a diff, where files exist to browse. */
60
+ export function DiffChangesToggle({
61
+ isDiff,
62
+ open,
63
+ onToggle,
64
+ theme,
65
+ }: {
66
+ isDiff: boolean;
67
+ open: boolean;
68
+ onToggle: () => void;
69
+ theme?: Theme;
70
+ }): React.ReactNode {
71
+ if (!isDiff) return null;
72
+
73
+ return (
74
+ <IconButton
75
+ glyph={open ? NERD.sidebarRight : NERD.sidebarRightOff}
76
+ onPress={onToggle}
77
+ marginLeft={2}
78
+ theme={theme}
79
+ />
80
+ );
81
+ }
82
+
83
+ export interface ChangesColumnProps {
84
+ open: boolean;
85
+ files?: readonly DiffFileContents[];
86
+ /** The path whose diff the sheet is scrolled to, highlighted in the tree. */
87
+ selectedPath?: string;
88
+ onSelectFile: (path: string) => void;
89
+ /** Comments per file path, shown as a dot-and-count badge on each entry. */
90
+ commentCounts?: ReadonlyMap<string, number>;
91
+ width?: number;
92
+ theme?: Theme;
93
+ }
94
+
95
+ /**
96
+ * The changed-files tree on its own, with no surrounding column frame: the body
97
+ * a workbench Project pane renders. A file click scrolls the diff to that file.
98
+ */
99
+ export function ChangesFileTree({
100
+ files = [],
101
+ selectedPath,
102
+ onSelectFile,
103
+ commentCounts,
104
+ theme,
105
+ }: Omit<ChangesColumnProps, "open" | "width">): React.ReactNode {
106
+ const nodes = useMemo(() => buildFileTree(files, commentCounts), [files, commentCounts]);
107
+ // every folder opens by default so each changed file is reachable; the user
108
+ // only ever names the folders they fold shut, so a new file set stays open
109
+ const [collapsedIds, setCollapsedIds] = useState<ReadonlySet<string>>(new Set());
110
+ const expandedIds = useMemo(() => {
111
+ const expanded = allFolderIds(nodes);
112
+
113
+ for (const id of collapsedIds) expanded.delete(id);
114
+
115
+ return expanded;
116
+ }, [nodes, collapsedIds]);
117
+
118
+ return (
119
+ <ScrollArea>
120
+ <Tree
121
+ nodes={nodes}
122
+ expandedIds={expandedIds}
123
+ selectedId={selectedPath}
124
+ flattenEmptyDirectories
125
+ showStatus
126
+ onSelect={onSelectFile}
127
+ onToggle={(id) =>
128
+ setCollapsedIds((current) => {
129
+ const next = new Set(current);
130
+
131
+ if (next.has(id)) next.delete(id);
132
+ else next.add(id);
133
+
134
+ return next;
135
+ })
136
+ }
137
+ theme={theme}
138
+ />
139
+ </ScrollArea>
140
+ );
141
+ }
142
+
143
+ export function ChangesColumn({
144
+ open,
145
+ files = [],
146
+ selectedPath,
147
+ onSelectFile,
148
+ width = 32,
149
+ theme,
150
+ }: ChangesColumnProps): React.ReactNode {
151
+ if (!open) return null;
152
+
153
+ return (
154
+ <box
155
+ style={{
156
+ width,
157
+ flexDirection: "column",
158
+ borderStyle: "single",
159
+ border: ["left"],
160
+ borderColor: theme?.border,
161
+ }}
162
+ >
163
+ <ChangesFileTree
164
+ files={files}
165
+ selectedPath={selectedPath}
166
+ onSelectFile={onSelectFile}
167
+ theme={theme}
168
+ />
169
+ </box>
170
+ );
171
+ }
@@ -0,0 +1,42 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { CodeBlock } from "./CodeBlock";
5
+
6
+ export const meta: StoryMeta = { title: "Primitives/CodeBlock" };
7
+
8
+ const SNIPPET = `export function gate(full: number) {
9
+ // threshold
10
+ return full < 0.6 ? "blocked" : "clear";
11
+ }`;
12
+
13
+ export const Typescript: Story = {
14
+ render: () => <CodeBlock language="ts" content={SNIPPET} />,
15
+ expectedColors: [DARK.elevated],
16
+ };
17
+
18
+ export const CursorAnnotatedEdited: Story = {
19
+ render: () => (
20
+ <CodeBlock
21
+ language="ts"
22
+ content={SNIPPET}
23
+ isCursor
24
+ isAnnotated
25
+ changeTag="edited"
26
+ marginTop={1}
27
+ />
28
+ ),
29
+ expectedColors: [DARK.accent],
30
+ };
31
+
32
+ export const UnknownLanguage: Story = {
33
+ render: () => (
34
+ <CodeBlock language="brainfog" content={"plain verbatim lines\n with indentation"} />
35
+ ),
36
+ };
37
+
38
+ /** A cut code block: struck-through gray content, no [cut] tag. */
39
+ export const Cut: Story = {
40
+ render: () => <CodeBlock language="ts" content={SNIPPET} cut />,
41
+ expectedColors: [DARK.elevated],
42
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Fenced code block: verbatim lines in a contained elevated box, highlighted
3
+ * by the native code renderable (tree-sitter). Unstyled text draws first, so
4
+ * the content never flashes empty while highlights resolve; unknown languages
5
+ * stay unstyled.
6
+ */
7
+
8
+ import React from "react";
9
+ import { createTextAttributes } from "@opentui/core";
10
+ import type { Theme } from "../theme";
11
+ import { useComponentTheme } from "./theme-context";
12
+ import { filetypeFor, syntaxStyleFor } from "./syntax-highlight";
13
+
14
+ /** A cut code block reads as removed: struck through and grayed, never red. */
15
+ const CUT_ATTRIBUTES = createTextAttributes({ strikethrough: true, dim: true });
16
+
17
+ export interface CodeBlockProps {
18
+ id?: string;
19
+ /** Markdown fence info string ("tsx", "python", ...). */
20
+ language?: string;
21
+ content: string;
22
+ isCursor?: boolean;
23
+ /** Vertical rhythm above the block. */
24
+ marginTop?: number;
25
+ isAnnotated?: boolean;
26
+ /** Working-copy change tag rendered after the language label. */
27
+ changeTag?: "new" | "edited";
28
+ /** A cut (removed) block: struck-through gray content, no tag. */
29
+ cut?: boolean;
30
+ theme?: Theme;
31
+ }
32
+
33
+ export function CodeBlock({
34
+ id,
35
+ language,
36
+ content,
37
+ isCursor = false,
38
+ marginTop = 0,
39
+ isAnnotated = false,
40
+ changeTag,
41
+ cut = false,
42
+ theme,
43
+ }: CodeBlockProps): React.ReactNode {
44
+ const tokens = useComponentTheme(theme);
45
+ const tagColor = changeTag === "new" ? tokens.green : tokens.accent;
46
+
47
+ return (
48
+ <box id={id} style={{ flexDirection: "column", marginTop }}>
49
+ <text>
50
+ <span fg={isCursor ? tokens.accent : tokens.textDim}>{isCursor ? "▎ " : " "}</span>
51
+ <span fg={tokens.textDim}>{language ?? "code"}</span>
52
+ {isAnnotated ? <span fg={tokens.accent}> ◆</span> : null}
53
+ {changeTag ? <span fg={tagColor}> [{changeTag}]</span> : null}
54
+ </text>
55
+ <box
56
+ style={{
57
+ flexDirection: "column",
58
+ backgroundColor: tokens.elevated,
59
+ paddingLeft: 2,
60
+ paddingRight: 2,
61
+ paddingTop: 1,
62
+ paddingBottom: 1,
63
+ marginLeft: 2,
64
+ }}
65
+ >
66
+ {cut ? (
67
+ <text style={{ wrapMode: "none" }} selectable={false}>
68
+ <span fg={tokens.textDim} attributes={CUT_ATTRIBUTES}>
69
+ {content}
70
+ </span>
71
+ </text>
72
+ ) : (
73
+ <code
74
+ content={content}
75
+ filetype={filetypeFor(language)}
76
+ syntaxStyle={syntaxStyleFor(tokens)}
77
+ selectable={false}
78
+ style={{ wrapMode: "none", fg: tokens.textMuted, bg: tokens.elevated }}
79
+ />
80
+ )}
81
+ </box>
82
+ </box>
83
+ );
84
+ }
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { CompletionOverlay } from "./CompletionOverlay";
5
+
6
+ export const meta: StoryMeta = { title: "Overlays/CompletionOverlay" };
7
+
8
+ export const ApprovedPrompt: Story = {
9
+ render: () => <CompletionOverlay verdict="approve" completion={{ phase: "prompt" }} status="" />,
10
+ expectedColors: [DARK.green],
11
+ };
12
+
13
+ export const FeedbackCounting: Story = {
14
+ render: () => (
15
+ <CompletionOverlay
16
+ verdict="request_changes"
17
+ completion={{ phase: "counting", remaining: 3 }}
18
+ status="exported to vault/cueloop/plan.md"
19
+ returnsTo="agent/worker-3"
20
+ />
21
+ ),
22
+ expectedColors: [DARK.accent],
23
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Post-submit hand-back overlay: the agent is unblocked, the reviewer either
3
+ * closes now, opts into auto-close, or dismisses back to the resolved view.
4
+ * The latest status line (e.g. the vault-export path) stays visible here.
5
+ */
6
+
7
+ import React from "react";
8
+ import type { VerdictKind } from "@cueloop/schema";
9
+ import type { Theme } from "../theme";
10
+ import { useComponentTheme } from "./theme-context";
11
+
12
+ export interface CompletionOverlayProps {
13
+ verdict: VerdictKind;
14
+ completion: { phase: "prompt" } | { phase: "counting"; remaining: number };
15
+ /** Latest status line (e.g. the vault-export path) stays visible here. */
16
+ status: string;
17
+ /** Where focus goes on close (the agent's pane), when known. */
18
+ returnsTo?: string;
19
+ theme?: Theme;
20
+ }
21
+
22
+ export function CompletionOverlay({
23
+ verdict,
24
+ completion,
25
+ status,
26
+ returnsTo,
27
+ theme,
28
+ }: CompletionOverlayProps): React.ReactNode {
29
+ const tokens = useComponentTheme(theme);
30
+ const approved = verdict === "approve";
31
+
32
+ return (
33
+ <box
34
+ style={{
35
+ width: "100%",
36
+ height: "100%",
37
+ backgroundColor: tokens.background,
38
+ flexDirection: "column",
39
+ justifyContent: "center",
40
+ alignItems: "center",
41
+ }}
42
+ >
43
+ <text fg={approved ? tokens.green : tokens.accent}>
44
+ {approved ? "review approved" : "feedback sent"}
45
+ </text>
46
+ <text> </text>
47
+ <text fg={tokens.text}>
48
+ The agent has your {approved ? "approval" : "feedback"} and is unblocked.
49
+ </text>
50
+ {returnsTo ? <text fg={tokens.textDim}>returning to {returnsTo} on close</text> : null}
51
+ {status ? <text fg={tokens.textDim}>{status}</text> : null}
52
+ <text> </text>
53
+ <text fg={tokens.textDim}>
54
+ close [return]
55
+ {completion.phase === "counting" ? ` · closing in ${completion.remaining}s` : ""}
56
+ {" · return to plan [esc] · always [a]"}
57
+ </text>
58
+ </box>
59
+ );
60
+ }
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { ConfirmCard } from "./ConfirmCard";
5
+
6
+ export const meta: StoryMeta = { title: "Cards/ConfirmCard" };
7
+
8
+ const callbacks = {
9
+ onInput: () => {},
10
+ onSelectVerdict: () => {},
11
+ onSubmit: () => {},
12
+ onCancel: () => {},
13
+ };
14
+
15
+ export const ApproveDefault: Story = {
16
+ render: () => <ConfirmCard verdict="approve" summary="" {...callbacks} />,
17
+ expectedColors: [DARK.green, DARK.accent],
18
+ size: { width: 40, height: 14 },
19
+ };
20
+
21
+ export const RequestChanges: Story = {
22
+ render: () => (
23
+ <ConfirmCard verdict="request_changes" summary="Tighten the steps." {...callbacks} />
24
+ ),
25
+ expectedColors: [DARK.red],
26
+ size: { width: 40, height: 14 },
27
+ };
28
+
29
+ export const WithViewedSummary: Story = {
30
+ render: () => (
31
+ <ConfirmCard verdict="approve" summary="" viewedSummary="2/3 files viewed" {...callbacks} />
32
+ ),
33
+ expectedColors: [DARK.green, DARK.textDim],
34
+ size: { width: 40, height: 14 },
35
+ };
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The Submit button expanded into a bordered confirm card at the rail bottom:
3
+ * the verdict selector (arrow keys or click), the optional summary, and plain
4
+ * word-buttons - key hints live in the status line only. The bordered height
5
+ * derives from the content rows through Card, so layout and render never drift.
6
+ */
7
+
8
+ import React from "react";
9
+ import type { VerdictKind } from "@cueloop/schema";
10
+ import type { Theme } from "../theme";
11
+ import { useComponentTheme } from "./theme-context";
12
+ import { Card } from "./primitives/Card";
13
+ import { Button } from "./primitives/Button";
14
+ import { Toolbar } from "./primitives/Toolbar";
15
+
16
+ export const VERDICTS: VerdictKind[] = ["comment", "approve", "request_changes"];
17
+
18
+ /** Selector words in the confirm card - one word per verdict. */
19
+ export const VERDICT_LABEL: Record<VerdictKind, string> = {
20
+ comment: "Comment",
21
+ approve: "Approve",
22
+ request_changes: "Changes",
23
+ };
24
+
25
+ export interface ConfirmCardProps {
26
+ verdict: VerdictKind;
27
+ summary: string;
28
+ /**
29
+ * The guided walk's honest coverage line for diff sessions, e.g.
30
+ * "2/3 files viewed". Undefined = no walk data, the row does not render.
31
+ */
32
+ viewedSummary?: string;
33
+ onInput: (summary: string) => void;
34
+ onSelectVerdict: (verdict: VerdictKind) => void;
35
+ onSubmit: () => void;
36
+ onCancel: () => void;
37
+ theme?: Theme;
38
+ }
39
+
40
+ /** 1-row verdict selector, spacer, summary input, spacer, buttons. */
41
+ const CONFIRM_CONTENT_ROWS = 5;
42
+
43
+ export function verdictColor(verdict: VerdictKind, tokens: Theme): string {
44
+ return verdict === "approve"
45
+ ? tokens.green
46
+ : verdict === "request_changes"
47
+ ? tokens.red
48
+ : tokens.blue;
49
+ }
50
+
51
+ /**
52
+ * The verdict selector: one row of pressable words, matching the reading
53
+ * direction of a choice between three peers. Selection stays controlled by
54
+ * the grammar (←/→ cycle the verdict); a click selects directly. The
55
+ * selected verdict wears brackets and its color.
56
+ */
57
+ function VerdictSelector({
58
+ verdict,
59
+ onSelectVerdict,
60
+ theme,
61
+ }: {
62
+ verdict: VerdictKind;
63
+ onSelectVerdict: (verdict: VerdictKind) => void;
64
+ theme?: Theme;
65
+ }): React.ReactNode {
66
+ const tokens = useComponentTheme(theme);
67
+
68
+ return (
69
+ <box style={{ flexDirection: "row", height: 1 }}>
70
+ {VERDICTS.map((candidate) => (
71
+ <box
72
+ key={candidate}
73
+ style={{ paddingRight: 1 }}
74
+ onMouseUp={() => onSelectVerdict(candidate)}
75
+ >
76
+ <text fg={candidate === verdict ? verdictColor(candidate, tokens) : tokens.textDim}>
77
+ {candidate === verdict
78
+ ? `[${VERDICT_LABEL[candidate]}]`
79
+ : ` ${VERDICT_LABEL[candidate]} `}
80
+ </text>
81
+ </box>
82
+ ))}
83
+ </box>
84
+ );
85
+ }
86
+
87
+ export function ConfirmCard({
88
+ verdict,
89
+ summary,
90
+ viewedSummary,
91
+ onInput,
92
+ onSelectVerdict,
93
+ onSubmit,
94
+ onCancel,
95
+ theme,
96
+ }: ConfirmCardProps): React.ReactNode {
97
+ const tokens = useComponentTheme(theme);
98
+
99
+ return (
100
+ <Card
101
+ title=" send message "
102
+ contentRows={CONFIRM_CONTENT_ROWS + (viewedSummary !== undefined ? 2 : 0)}
103
+ borderColor={tokens.text}
104
+ backgroundColor={tokens.elevated}
105
+ theme={theme}
106
+ >
107
+ {viewedSummary !== undefined ? <text fg={tokens.textDim}>{viewedSummary}</text> : null}
108
+ {viewedSummary !== undefined ? <box style={{ height: 1 }} /> : null}
109
+ <VerdictSelector verdict={verdict} onSelectVerdict={onSelectVerdict} theme={theme} />
110
+ <box style={{ height: 1 }} />
111
+ <input
112
+ focused
113
+ value={summary}
114
+ onInput={onInput}
115
+ placeholder="summary for the agent (optional)"
116
+ />
117
+ <box style={{ height: 1 }} />
118
+ <Toolbar>
119
+ <Button variant="solid" marginRight={2} onPress={onSubmit} theme={theme}>
120
+ {" send message "}
121
+ </Button>
122
+ <Button onPress={onCancel} theme={theme}>
123
+ {" Cancel "}
124
+ </Button>
125
+ </Toolbar>
126
+ </Card>
127
+ );
128
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { ConfirmDialog } from "./ConfirmDialog";
5
+
6
+ export const meta: StoryMeta = { title: "Overlays/ConfirmDialog" };
7
+
8
+ const callbacks = { onConfirm: () => {}, onCancel: () => {} };
9
+
10
+ export const DeletePlan: Story = {
11
+ render: () => (
12
+ <ConfirmDialog
13
+ isOpen
14
+ title=" Delete plan "
15
+ message={'Delete "Auth rollout"? This removes the plan and its review.'}
16
+ {...callbacks}
17
+ />
18
+ ),
19
+ expectedColors: [DARK.accent],
20
+ size: { width: 64, height: 16 },
21
+ };