@cueloop/client 0.1.0-alpha.9 → 0.1.0-alpha.90
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.
- package/native/build-pty.sh +51 -0
- package/native/build.sh +82 -0
- package/native/darwin-arm64/libcuelooppty.dylib +0 -0
- package/native/darwin-arm64/libcueloopvt.dylib +0 -0
- package/native/src/pty.zig +193 -0
- package/native/src/shim.zig +114 -0
- package/package.json +42 -34
- package/src/App.appearance.test.tsx +53 -0
- package/src/App.autoclose.test.tsx +77 -51
- package/src/App.diff.test.tsx +259 -45
- package/src/App.export.test.tsx +58 -41
- package/src/App.focus.test.ts +89 -0
- package/src/App.identity.test.tsx +78 -0
- package/src/App.plan-render.test.tsx +209 -0
- package/src/App.plan-review.test.tsx +330 -161
- package/src/App.prototype-markdown.test.tsx +128 -0
- package/src/App.prototype.test.tsx +176 -0
- package/src/App.readonly.test.tsx +129 -67
- package/src/App.regression.test.tsx +94 -0
- package/src/App.rendering.test.tsx +61 -35
- package/src/App.share-dialog.test.tsx +172 -0
- package/src/App.submit-confirm.test.tsx +264 -0
- package/src/App.test.tsx +402 -126
- package/src/App.toast.test.tsx +98 -0
- package/src/App.tsx +1402 -1060
- package/src/App.visual.test.tsx +57 -0
- package/src/App.walk.test.tsx +299 -0
- package/src/App.workbench.test.tsx +774 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +100 -0
- package/src/annotation-palette.ts +62 -0
- package/src/ansi-screen.test.ts +28 -0
- package/src/ansi-screen.ts +72 -0
- package/src/app-key-state.ts +61 -0
- package/src/app-screens.tsx +577 -0
- package/src/app-view-model.ts +168 -0
- package/src/attribution.test.ts +31 -0
- package/src/attribution.ts +42 -0
- package/src/client-extension-registry.test.tsx +61 -0
- package/src/client-extension-registry.ts +159 -0
- package/src/clipboard.test.ts +23 -0
- package/src/clipboard.ts +33 -0
- package/src/components/AnnotatableFileView.stories.tsx +42 -0
- package/src/components/AnnotatableFileView.test.tsx +102 -0
- package/src/components/AnnotatableFileView.tsx +134 -0
- package/src/components/AnnotationCards.stories.tsx +86 -0
- package/src/components/AnnotationCards.tsx +341 -0
- package/src/components/AnsiScreen.stories.tsx +12 -0
- package/src/components/AnsiScreen.tsx +28 -0
- package/src/components/AppHeader.stories.tsx +50 -0
- package/src/components/AppHeader.tsx +83 -0
- package/src/components/AppShell.brand.test.tsx +58 -0
- package/src/components/AppShell.extensions.test.tsx +191 -0
- package/src/components/AppShell.stories.tsx +89 -0
- package/src/components/AppShell.tsx +362 -0
- package/src/components/AppShell.zoom.test.tsx +135 -0
- package/src/components/BareWorkbenchFileView.stories.tsx +27 -0
- package/src/components/BareWorkbenchFileView.tsx +64 -0
- package/src/components/Breadcrumb.stories.tsx +19 -0
- package/src/components/Breadcrumb.tsx +45 -0
- package/src/components/ChangesColumn.stories.tsx +44 -0
- package/src/components/ChangesColumn.tsx +199 -0
- package/src/components/ChangesFileTree.test.tsx +61 -0
- package/src/components/CodeBlock.stories.tsx +42 -0
- package/src/components/CodeBlock.tsx +84 -0
- package/src/components/CompletionOverlay.stories.tsx +37 -0
- package/src/components/CompletionOverlay.tsx +77 -0
- package/src/components/ConfirmCard.stories.tsx +37 -0
- package/src/components/ConfirmCard.tsx +150 -0
- package/src/components/ConfirmDialog.stories.tsx +21 -0
- package/src/components/ConfirmDialog.tsx +72 -0
- package/src/components/DiffContentView.scroll.test.tsx +149 -0
- package/src/components/DiffContentView.split.test.tsx +140 -0
- package/src/components/DiffContentView.stories.tsx +80 -0
- package/src/components/DiffContentView.syntax.test.tsx +82 -0
- package/src/components/DiffContentView.tsx +906 -0
- package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/components/DiscussionMarkerRail.tsx +186 -0
- package/src/components/EditorGrid.stories.tsx +59 -0
- package/src/components/EditorGrid.tabs.test.tsx +118 -0
- package/src/components/EditorGrid.tsx +426 -0
- package/src/components/EditorGrid.zoom.test.tsx +68 -0
- package/src/components/ExtensionZone.stories.tsx +28 -0
- package/src/components/ExtensionZone.tsx +145 -0
- package/src/components/FileContentsView.stories.tsx +16 -0
- package/src/components/FileContentsView.test.tsx +29 -0
- package/src/components/FileContentsView.tsx +98 -0
- package/src/components/GridTabContent.stories.tsx +59 -0
- package/src/components/GridTabContent.tsx +182 -0
- package/src/components/HandleChipsInput.stories.tsx +24 -0
- package/src/components/HandleChipsInput.test.tsx +35 -0
- package/src/components/HandleChipsInput.tsx +145 -0
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +103 -0
- package/src/components/MarkdownGridBlock.stories.tsx +33 -0
- package/src/components/MarkdownGridBlock.tsx +72 -0
- package/src/components/MarkdownThreadEditor.stories.tsx +15 -0
- package/src/components/MarkdownThreadEditor.test.tsx +98 -0
- package/src/components/MarkdownThreadEditor.tsx +162 -0
- package/src/components/MenuBar.stories.tsx +28 -0
- package/src/components/MenuBar.tsx +44 -0
- package/src/components/NavModeHint.stories.tsx +40 -0
- package/src/components/NavModeHint.tsx +31 -0
- package/src/components/OverlayScrollbar.stories.tsx +33 -0
- package/src/components/OverlayScrollbar.test.tsx +139 -0
- package/src/components/OverlayScrollbar.tsx +129 -0
- package/src/components/PaneGrid.stories.tsx +84 -0
- package/src/components/PaneGrid.tsx +128 -0
- package/src/components/PanelColumn.stories.tsx +25 -0
- package/src/components/PanelColumn.tsx +98 -0
- package/src/components/ProjectTreeView.stories.tsx +25 -0
- package/src/components/ProjectTreeView.test.tsx +75 -0
- package/src/components/ProjectTreeView.tsx +125 -0
- package/src/components/PromptDialog.stories.tsx +36 -0
- package/src/components/PromptDialog.test.tsx +61 -0
- package/src/components/PromptDialog.tsx +99 -0
- package/src/components/PrototypeContentView.stories.tsx +19 -0
- package/src/components/PrototypeContentView.tsx +446 -0
- package/src/components/RootOverlay.stories.tsx +45 -0
- package/src/components/RootOverlay.tsx +48 -0
- package/src/components/ScrollArea.stories.tsx +20 -0
- package/src/components/ScrollArea.tsx +44 -0
- package/src/components/SettingsDialog.stories.tsx +117 -0
- package/src/components/SettingsDialog.tsx +186 -0
- package/src/components/SettingsRows.stories.tsx +18 -0
- package/src/components/SettingsRows.tsx +112 -0
- package/src/components/ShareDialog.stories.tsx +121 -0
- package/src/components/ShareDialog.tsx +552 -0
- package/src/components/ShellHeader.stories.tsx +50 -0
- package/src/components/ShellHeader.tsx +137 -0
- package/src/components/SlashComposer.stories.tsx +37 -0
- package/src/components/SlashComposer.tsx +87 -0
- package/src/components/ThreadFooter.stories.tsx +30 -0
- package/src/components/ThreadFooter.test.tsx +47 -0
- package/src/components/ThreadFooter.tsx +76 -0
- package/src/components/ThreadTree.stories.tsx +46 -0
- package/src/components/ThreadTree.tsx +255 -0
- package/src/components/ThreadView.selection.test.tsx +951 -0
- package/src/components/ThreadView.slash.test.tsx +146 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +525 -0
- package/src/components/Toast.stories.tsx +19 -0
- package/src/components/Toast.tsx +52 -0
- package/src/components/Tooltip.stories.tsx +26 -0
- package/src/components/Tooltip.tsx +72 -0
- package/src/components/TreePane.stories.tsx +106 -0
- package/src/components/TreePane.tsx +157 -0
- package/src/components/WalkWizard.stories.tsx +100 -0
- package/src/components/WalkWizard.tsx +150 -0
- package/src/components/WelcomePlayground.stories.tsx +12 -0
- package/src/components/WelcomePlayground.test.tsx +49 -0
- package/src/components/WelcomePlayground.tsx +139 -0
- package/src/components/WorkspacePanels.stories.tsx +36 -0
- package/src/components/WorkspacePanels.tsx +116 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +2264 -0
- package/src/components/diff-content-view-layout.test.ts +198 -0
- package/src/components/diff-content-view-layout.ts +84 -0
- package/src/components/editor-grid.test.ts +86 -0
- package/src/components/editor-grid.ts +138 -0
- package/src/components/file-tree.test.ts +60 -0
- package/src/components/file-tree.ts +112 -0
- package/src/components/menu-control.stories.tsx +35 -0
- package/src/components/menu-control.tsx +43 -0
- package/src/components/pages/Workspace.stories.tsx +181 -0
- package/src/components/primitives/Button.stories.tsx +32 -0
- package/src/components/primitives/Button.tsx +59 -0
- package/src/components/primitives/Card.stories.tsx +25 -0
- package/src/components/primitives/Card.tsx +63 -0
- package/src/components/primitives/Dialog.stories.tsx +24 -0
- package/src/components/primitives/Dialog.test.tsx +37 -0
- package/src/components/primitives/Dialog.tsx +71 -0
- package/src/components/primitives/DialogActions.stories.tsx +20 -0
- package/src/components/primitives/DialogActions.tsx +39 -0
- package/src/components/primitives/IconButton.stories.tsx +36 -0
- package/src/components/primitives/IconButton.tsx +62 -0
- package/src/components/primitives/StatusBar.stories.tsx +11 -0
- package/src/components/primitives/StatusBar.tsx +23 -0
- package/src/components/primitives/Switch.stories.tsx +17 -0
- package/src/components/primitives/Switch.tsx +32 -0
- package/src/components/primitives/Tabs.stories.tsx +29 -0
- package/src/components/primitives/Tabs.tsx +102 -0
- package/src/components/primitives/Toolbar.stories.tsx +17 -0
- package/src/components/primitives/Toolbar.tsx +14 -0
- package/src/components/primitives/Tree.stories.tsx +124 -0
- package/src/components/primitives/Tree.tsx +100 -0
- package/src/components/primitives/frame.test.ts +12 -0
- package/src/components/primitives/frame.ts +17 -0
- package/src/components/primitives/icons.ts +61 -0
- package/src/components/primitives/tree-model.test.ts +99 -0
- package/src/components/primitives/tree-model.ts +137 -0
- package/src/components/quick-actions-editor.stories.tsx +58 -0
- package/src/components/quick-actions-editor.test.tsx +117 -0
- package/src/components/quick-actions-editor.tsx +226 -0
- package/src/components/share-dialog-store.ts +58 -0
- package/src/components/stories-app.tsx +193 -0
- package/src/components/stories.test.tsx +74 -0
- package/src/components/story-fixtures.ts +102 -0
- package/src/components/story-tree.test.ts +108 -0
- package/src/components/story.ts +159 -0
- package/src/components/syntax-highlight.ts +133 -0
- package/src/components/terminal-pane.test.tsx +40 -0
- package/src/components/terminal-pane.ts +199 -0
- package/src/components/theme-context.stories.tsx +46 -0
- package/src/components/theme-context.tsx +37 -0
- package/src/components/thread-tree.test.ts +134 -0
- package/src/components/thread-tree.ts +109 -0
- package/src/components/truncate-text.ts +7 -0
- package/src/components/truncate-title.test.ts +31 -0
- package/src/components/truncate-title.ts +15 -0
- package/src/config-identity.test.ts +78 -0
- package/src/config.test.ts +579 -34
- package/src/config.ts +533 -57
- package/src/diff-fold.test.ts +78 -0
- package/src/diff-fold.ts +126 -0
- package/src/diff-hunk-curate.test.ts +315 -0
- package/src/diff-hunk-curate.ts +153 -0
- package/src/diff-intraline.test.ts +238 -0
- package/src/diff-intraline.ts +236 -0
- package/src/diff-syntax.test.ts +112 -0
- package/src/diff-syntax.ts +179 -0
- package/src/discussions.ts +79 -0
- package/src/editor.test.ts +149 -0
- package/src/editor.ts +139 -9
- package/src/ghostty-terminal.test.ts +85 -0
- package/src/ghostty-terminal.ts +199 -0
- package/src/github-identity.test.ts +58 -0
- package/src/github-identity.ts +41 -0
- package/src/herdr.test.ts +9 -34
- package/src/herdr.ts +25 -21
- package/src/index.ts +28 -0
- package/src/integrations.test.ts +41 -0
- package/src/integrations.ts +32 -0
- package/src/intent-dispatch.test.ts +724 -0
- package/src/intent-dispatch.ts +690 -0
- package/src/key-bindings.test.ts +156 -0
- package/src/key-bindings.ts +459 -0
- package/src/keymap.test.ts +456 -115
- package/src/keymap.ts +315 -81
- package/src/kitty-image.test.ts +66 -0
- package/src/kitty-image.ts +138 -0
- package/src/launch-layout.test.ts +67 -0
- package/src/launch-layout.ts +47 -0
- package/src/mark-runs.ts +121 -0
- package/src/markdown-editor-syntax-style.ts +50 -0
- package/src/markdown-grid.test.ts +101 -0
- package/src/markdown-grid.ts +203 -0
- package/src/markdown-highlight.test.ts +61 -0
- package/src/markdown-highlight.ts +122 -0
- package/src/pasted-image.test.ts +67 -0
- package/src/pasted-image.ts +60 -0
- package/src/perf/otlp-trace.test.ts +116 -0
- package/src/perf/otlp-trace.ts +130 -0
- package/src/perf/perf-report.ts +17 -0
- package/src/perf/perf-timings.test.ts +18 -0
- package/src/perf/perf-timings.ts +38 -0
- package/src/prototype-browser.test.ts +81 -0
- package/src/prototype-browser.ts +329 -0
- package/src/prototype-pixels.tsx +35 -0
- package/src/pty.test.ts +81 -0
- package/src/pty.ts +245 -0
- package/src/ready-signal.test.ts +85 -0
- package/src/ready-signal.ts +52 -0
- package/src/run.ts +85 -12
- package/src/serve.test.ts +24 -12
- package/src/serve.ts +52 -10
- package/src/share.ts +237 -0
- package/src/skills.test.ts +74 -0
- package/src/skills.ts +75 -0
- package/src/slash-palette.test.ts +103 -0
- package/src/slash-palette.ts +129 -0
- package/src/split-diff.test.ts +110 -0
- package/src/split-diff.ts +149 -0
- package/src/test-support.test.ts +264 -0
- package/src/test-support.ts +369 -0
- package/src/theme-presets.test.ts +51 -0
- package/src/theme-presets.ts +219 -0
- package/src/theme.ts +88 -19
- package/src/thread-chords.test.ts +79 -0
- package/src/thread-chords.ts +163 -0
- package/src/thread-code-syntax.ts +45 -0
- package/src/thread-controller.curate.test.ts +476 -0
- package/src/thread-controller.derivation.test.ts +62 -0
- package/src/thread-controller.live-diff.test.ts +322 -0
- package/src/thread-controller.optimistic.test.ts +69 -0
- package/src/thread-controller.share.test.ts +561 -0
- package/src/thread-controller.switch-cache.test.ts +176 -0
- package/src/thread-controller.tree.test.ts +261 -0
- package/src/thread-controller.ts +2050 -0
- package/src/thread-selection.test.ts +210 -0
- package/src/thread-selection.ts +243 -0
- package/src/tree-view.test.ts +128 -0
- package/src/tree-view.ts +153 -0
- package/src/use-annotation-surface.tsx +975 -0
- package/src/use-changes-workbench.ts +190 -0
- package/src/use-frame-measure.ts +41 -0
- package/src/use-remember-layout.ts +31 -0
- package/src/use-repo-changes.ts +87 -0
- package/src/use-settings-dialog.tsx +364 -0
- package/src/use-terminal-virtualizer.ts +259 -0
- package/src/use-thread-body-editing.tsx +58 -0
- package/src/version.ts +5 -0
- package/src/view-diff.test.ts +279 -9
- package/src/view-diff.ts +460 -24
- package/src/view-plan.regression.test.ts +60 -0
- package/src/view-plan.test.ts +545 -0
- package/src/view-plan.ts +1019 -0
- package/src/visual/contrast.test.ts +35 -0
- package/src/visual/contrast.ts +33 -0
- package/src/walk.test.ts +121 -0
- package/src/walk.ts +74 -0
- package/src/workbench-snapshot.test.ts +99 -0
- package/src/workbench-snapshot.ts +59 -0
- package/src/session-controller.ts +0 -398
- package/src/syntax.test.ts +0 -41
- package/src/syntax.ts +0 -88
- package/src/view.test.ts +0 -157
- package/src/view.ts +0 -364
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
{
|
|
22
|
+
path: "packages/daemon/src/thread-review.ts",
|
|
23
|
+
oldContents: "d",
|
|
24
|
+
newContents: "",
|
|
25
|
+
status: "deleted",
|
|
26
|
+
},
|
|
27
|
+
{ path: "README.md", oldContents: "e", newContents: "f", status: "modified" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const Changed: Story = {
|
|
31
|
+
render: () => (
|
|
32
|
+
<Frame>
|
|
33
|
+
<ChangesColumn
|
|
34
|
+
open
|
|
35
|
+
files={files}
|
|
36
|
+
selectedPath="packages/client/src/App.tsx"
|
|
37
|
+
onSelectFile={() => {}}
|
|
38
|
+
theme={DARK}
|
|
39
|
+
/>
|
|
40
|
+
</Frame>
|
|
41
|
+
),
|
|
42
|
+
expectedColors: [DARK.accent, DARK.border],
|
|
43
|
+
size: { width: 60, height: 12 },
|
|
44
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
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 { useKeyboard } from "@opentui/react";
|
|
9
|
+
import type { DiffFileContents, Thread } from "@cueloop/schema";
|
|
10
|
+
import type { Theme } from "../theme";
|
|
11
|
+
import type { DiffRow } from "../view-diff";
|
|
12
|
+
import { Tree } from "./primitives/Tree";
|
|
13
|
+
import { allFolderIds, flattenTree } from "./primitives/tree-model";
|
|
14
|
+
import { IconButton } from "./primitives/IconButton";
|
|
15
|
+
import { NERD } from "./primitives/icons";
|
|
16
|
+
import { buildFileTree } from "./file-tree";
|
|
17
|
+
|
|
18
|
+
export interface DiffColumnsState {
|
|
19
|
+
changesOpen: boolean;
|
|
20
|
+
toggleChanges: () => void;
|
|
21
|
+
/** The path under the diff cursor, highlighted in the Changes tree. */
|
|
22
|
+
currentFilePath?: string;
|
|
23
|
+
scrollToFile: (path: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Owns the right Changes column for a diff: context opens it when you enter a
|
|
28
|
+
* diff thread, and a file click scrolls the sheet to that file's first row.
|
|
29
|
+
* Resets on session change via the guarded render-time pattern, no effect.
|
|
30
|
+
*/
|
|
31
|
+
export function useDiffColumns(params: {
|
|
32
|
+
session: Thread | null;
|
|
33
|
+
rows: DiffRow[];
|
|
34
|
+
cursor: number;
|
|
35
|
+
setCursor: (index: number) => void;
|
|
36
|
+
}): DiffColumnsState {
|
|
37
|
+
const { session, rows, cursor, setCursor } = params;
|
|
38
|
+
const contextDefault = session?.artifact.type === "diff";
|
|
39
|
+
const [changesOpen, setChangesOpen] = useState(contextDefault);
|
|
40
|
+
const [seenSessionId, setSeenSessionId] = useState(session?.id);
|
|
41
|
+
|
|
42
|
+
// entering another thread lets context decide again; within a thread the manual toggle stays
|
|
43
|
+
if (session?.id !== seenSessionId) {
|
|
44
|
+
setSeenSessionId(session?.id);
|
|
45
|
+
setChangesOpen(contextDefault);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
changesOpen,
|
|
50
|
+
toggleChanges: () => setChangesOpen((open) => !open),
|
|
51
|
+
currentFilePath: rows[cursor]?.file,
|
|
52
|
+
scrollToFile: (path: string) => {
|
|
53
|
+
const fileRowIndex = rows.findIndex((row) => row.kind === "file" && row.file === path);
|
|
54
|
+
|
|
55
|
+
if (fileRowIndex >= 0) setCursor(fileRowIndex);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The header toggle for the Changes column; shown only for a diff, where files exist to browse. */
|
|
61
|
+
export function DiffChangesToggle({
|
|
62
|
+
isDiff,
|
|
63
|
+
open,
|
|
64
|
+
onToggle,
|
|
65
|
+
theme,
|
|
66
|
+
}: {
|
|
67
|
+
isDiff: boolean;
|
|
68
|
+
open: boolean;
|
|
69
|
+
onToggle: () => void;
|
|
70
|
+
theme?: Theme;
|
|
71
|
+
}): React.ReactNode {
|
|
72
|
+
if (!isDiff) return null;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<IconButton
|
|
76
|
+
glyph={open ? NERD.sidebarRight : NERD.sidebarRightOff}
|
|
77
|
+
onPress={onToggle}
|
|
78
|
+
marginLeft={2}
|
|
79
|
+
theme={theme}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ChangesColumnProps {
|
|
85
|
+
open: boolean;
|
|
86
|
+
files?: readonly DiffFileContents[];
|
|
87
|
+
/** The path whose diff the sheet is scrolled to, highlighted in the tree. */
|
|
88
|
+
selectedPath?: string;
|
|
89
|
+
onSelectFile: (path: string) => void;
|
|
90
|
+
/** Comments per file path, shown as a dot-and-count badge on each entry. */
|
|
91
|
+
commentCounts?: ReadonlyMap<string, number>;
|
|
92
|
+
focused?: boolean;
|
|
93
|
+
width?: number;
|
|
94
|
+
theme?: Theme;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The changed-files tree on its own, with no surrounding column frame: the body
|
|
99
|
+
* a workbench Project pane renders. A file click scrolls the diff to that file.
|
|
100
|
+
*/
|
|
101
|
+
export function ChangesFileTree({
|
|
102
|
+
files = [],
|
|
103
|
+
selectedPath,
|
|
104
|
+
onSelectFile,
|
|
105
|
+
commentCounts,
|
|
106
|
+
focused = false,
|
|
107
|
+
theme,
|
|
108
|
+
}: Omit<ChangesColumnProps, "open" | "width">): React.ReactNode {
|
|
109
|
+
const nodes = useMemo(() => buildFileTree(files, commentCounts), [files, commentCounts]);
|
|
110
|
+
// every folder opens by default so each changed file is reachable; the user
|
|
111
|
+
// only ever names the folders they fold shut, so a new file set stays open
|
|
112
|
+
const [collapsedIds, setCollapsedIds] = useState<ReadonlySet<string>>(new Set());
|
|
113
|
+
const [cursor, setCursor] = useState(0);
|
|
114
|
+
const expandedIds = useMemo(() => {
|
|
115
|
+
const expanded = allFolderIds(nodes);
|
|
116
|
+
|
|
117
|
+
for (const id of collapsedIds) expanded.delete(id);
|
|
118
|
+
|
|
119
|
+
return expanded;
|
|
120
|
+
}, [nodes, collapsedIds]);
|
|
121
|
+
const rows = useMemo(
|
|
122
|
+
() => flattenTree(nodes, { expandedIds, flattenEmptyDirectories: true }),
|
|
123
|
+
[nodes, expandedIds],
|
|
124
|
+
);
|
|
125
|
+
const cursorIndex = Math.min(cursor, Math.max(0, rows.length - 1));
|
|
126
|
+
|
|
127
|
+
const toggle = (id: string): void =>
|
|
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
|
+
useKeyboard((key) => {
|
|
138
|
+
if (!focused || rows.length === 0) return;
|
|
139
|
+
if (key.name === "j" || key.name === "down")
|
|
140
|
+
return setCursor(Math.min(cursorIndex + 1, rows.length - 1));
|
|
141
|
+
if (key.name === "k" || key.name === "up") return setCursor(Math.max(cursorIndex - 1, 0));
|
|
142
|
+
const row = rows[cursorIndex];
|
|
143
|
+
if (!row) return;
|
|
144
|
+
if (key.name === "tab" || key.name === "return" || key.name === "enter")
|
|
145
|
+
return row.isFolder ? toggle(row.id) : onSelectFile(row.id);
|
|
146
|
+
if (key.name === "l" && row.isFolder && collapsedIds.has(row.id)) return toggle(row.id);
|
|
147
|
+
if (key.name === "h" && row.isFolder && !collapsedIds.has(row.id)) return toggle(row.id);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<ScrollArea>
|
|
152
|
+
<Tree
|
|
153
|
+
nodes={nodes}
|
|
154
|
+
expandedIds={expandedIds}
|
|
155
|
+
selectedId={focused ? rows[cursorIndex]?.id : selectedPath}
|
|
156
|
+
flattenEmptyDirectories
|
|
157
|
+
showStatus
|
|
158
|
+
onSelect={(id) => {
|
|
159
|
+
const index = rows.findIndex((row) => row.id === id);
|
|
160
|
+
|
|
161
|
+
if (index >= 0) setCursor(index);
|
|
162
|
+
onSelectFile(id);
|
|
163
|
+
}}
|
|
164
|
+
onToggle={toggle}
|
|
165
|
+
theme={theme}
|
|
166
|
+
/>
|
|
167
|
+
</ScrollArea>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function ChangesColumn({
|
|
172
|
+
open,
|
|
173
|
+
files = [],
|
|
174
|
+
selectedPath,
|
|
175
|
+
onSelectFile,
|
|
176
|
+
width = 32,
|
|
177
|
+
theme,
|
|
178
|
+
}: ChangesColumnProps): React.ReactNode {
|
|
179
|
+
if (!open) return null;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<box
|
|
183
|
+
style={{
|
|
184
|
+
width,
|
|
185
|
+
flexDirection: "column",
|
|
186
|
+
borderStyle: "single",
|
|
187
|
+
border: ["left"],
|
|
188
|
+
borderColor: theme?.border,
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
<ChangesFileTree
|
|
192
|
+
files={files}
|
|
193
|
+
selectedPath={selectedPath}
|
|
194
|
+
onSelectFile={onSelectFile}
|
|
195
|
+
theme={theme}
|
|
196
|
+
/>
|
|
197
|
+
</box>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** The changed-files tree navigates by keyboard when focused: j/k move the cursor, tab folds a folder
|
|
2
|
+
* or opens a file. */
|
|
3
|
+
|
|
4
|
+
import { test, expect } from "bun:test";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { testRender } from "@opentui/react/test-utils";
|
|
7
|
+
import type { DiffFileContents } from "@cueloop/schema";
|
|
8
|
+
import { ChangesFileTree } from "./ChangesColumn";
|
|
9
|
+
import { settle, waitForText } from "../test-support";
|
|
10
|
+
import { DARK } from "../theme";
|
|
11
|
+
|
|
12
|
+
const file = (path: string): DiffFileContents => ({
|
|
13
|
+
path,
|
|
14
|
+
oldContents: "",
|
|
15
|
+
newContents: "x",
|
|
16
|
+
status: "modified",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const files = [file("src/a.ts"), file("src/b.ts")];
|
|
20
|
+
|
|
21
|
+
test("j moves onto a file and tab opens it", async () => {
|
|
22
|
+
const opened: string[] = [];
|
|
23
|
+
const setup = await testRender(
|
|
24
|
+
<ChangesFileTree
|
|
25
|
+
files={files}
|
|
26
|
+
onSelectFile={(path) => opened.push(path)}
|
|
27
|
+
focused
|
|
28
|
+
theme={DARK}
|
|
29
|
+
/>,
|
|
30
|
+
{ width: 40, height: 12 },
|
|
31
|
+
);
|
|
32
|
+
await settle(setup);
|
|
33
|
+
await waitForText(setup, "a.ts");
|
|
34
|
+
|
|
35
|
+
// folders open by default; cursor starts on the "src" folder, j steps onto a.ts
|
|
36
|
+
setup.mockInput.pressKey("j");
|
|
37
|
+
await settle(setup);
|
|
38
|
+
setup.mockInput.pressKey("TAB");
|
|
39
|
+
await settle(setup);
|
|
40
|
+
|
|
41
|
+
expect(opened).toEqual(["src/a.ts"]);
|
|
42
|
+
|
|
43
|
+
setup.renderer.destroy();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("without focus the keyboard opens nothing", async () => {
|
|
47
|
+
const opened: string[] = [];
|
|
48
|
+
const setup = await testRender(
|
|
49
|
+
<ChangesFileTree files={files} onSelectFile={(path) => opened.push(path)} theme={DARK} />,
|
|
50
|
+
{ width: 40, height: 12 },
|
|
51
|
+
);
|
|
52
|
+
await settle(setup);
|
|
53
|
+
await waitForText(setup, "a.ts");
|
|
54
|
+
|
|
55
|
+
setup.mockInput.pressKey("j");
|
|
56
|
+
setup.mockInput.pressKey("TAB");
|
|
57
|
+
await settle(setup);
|
|
58
|
+
expect(opened).toEqual([]);
|
|
59
|
+
|
|
60
|
+
setup.renderer.destroy();
|
|
61
|
+
});
|
|
@@ -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,37 @@
|
|
|
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
|
+
const noop = () => {};
|
|
9
|
+
|
|
10
|
+
export const ApprovedPrompt: Story = {
|
|
11
|
+
render: () => (
|
|
12
|
+
<CompletionOverlay
|
|
13
|
+
message="approved"
|
|
14
|
+
completion={{ phase: "prompt" }}
|
|
15
|
+
status=""
|
|
16
|
+
onClose={noop}
|
|
17
|
+
onBackToPlan={noop}
|
|
18
|
+
onAlways={noop}
|
|
19
|
+
/>
|
|
20
|
+
),
|
|
21
|
+
expectedColors: [DARK.green],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const FeedbackCounting: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<CompletionOverlay
|
|
27
|
+
message="changes_requested"
|
|
28
|
+
completion={{ phase: "counting", remaining: 3 }}
|
|
29
|
+
status="exported to vault/cueloop/plan.md"
|
|
30
|
+
returnsTo="agent/worker-3"
|
|
31
|
+
onClose={noop}
|
|
32
|
+
onBackToPlan={noop}
|
|
33
|
+
onAlways={noop}
|
|
34
|
+
/>
|
|
35
|
+
),
|
|
36
|
+
expectedColors: [DARK.accent],
|
|
37
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
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 { MessageOutcome } from "@cueloop/schema";
|
|
9
|
+
import type { Theme } from "../theme";
|
|
10
|
+
import { useComponentTheme } from "./theme-context";
|
|
11
|
+
import { Toolbar } from "./primitives/Toolbar";
|
|
12
|
+
import { Button } from "./primitives/Button";
|
|
13
|
+
|
|
14
|
+
export interface CompletionOverlayProps {
|
|
15
|
+
message: MessageOutcome;
|
|
16
|
+
completion: { phase: "prompt" } | { phase: "counting"; remaining: number };
|
|
17
|
+
/** Latest status line (e.g. the vault-export path) stays visible here. */
|
|
18
|
+
status: string;
|
|
19
|
+
/** Where focus goes on close (the agent's pane), when known. */
|
|
20
|
+
returnsTo?: string;
|
|
21
|
+
onClose: () => void;
|
|
22
|
+
onBackToPlan: () => void;
|
|
23
|
+
onAlways: () => void;
|
|
24
|
+
theme?: Theme;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function CompletionOverlay({
|
|
28
|
+
message,
|
|
29
|
+
completion,
|
|
30
|
+
status,
|
|
31
|
+
returnsTo,
|
|
32
|
+
onClose,
|
|
33
|
+
onBackToPlan,
|
|
34
|
+
onAlways,
|
|
35
|
+
theme,
|
|
36
|
+
}: CompletionOverlayProps): React.ReactNode {
|
|
37
|
+
const tokens = useComponentTheme(theme);
|
|
38
|
+
const approved = message === "approved";
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<box
|
|
42
|
+
style={{
|
|
43
|
+
width: "100%",
|
|
44
|
+
height: "100%",
|
|
45
|
+
backgroundColor: tokens.background,
|
|
46
|
+
flexDirection: "column",
|
|
47
|
+
justifyContent: "center",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<text fg={approved ? tokens.green : tokens.accent}>
|
|
52
|
+
{approved ? "review approved" : "feedback sent"}
|
|
53
|
+
</text>
|
|
54
|
+
<text> </text>
|
|
55
|
+
<text fg={tokens.text}>
|
|
56
|
+
The agent has your {approved ? "approval" : "feedback"} and is unblocked.
|
|
57
|
+
</text>
|
|
58
|
+
{returnsTo ? <text fg={tokens.textDim}>returning to {returnsTo} on close</text> : null}
|
|
59
|
+
{status ? <text fg={tokens.textDim}>{status}</text> : null}
|
|
60
|
+
{completion.phase === "counting" ? (
|
|
61
|
+
<text fg={tokens.textDim}>closing in {completion.remaining}s</text>
|
|
62
|
+
) : null}
|
|
63
|
+
<text> </text>
|
|
64
|
+
<Toolbar>
|
|
65
|
+
<Button variant="solid" marginRight={2} onPress={onClose} theme={theme}>
|
|
66
|
+
{" close "}
|
|
67
|
+
</Button>
|
|
68
|
+
<Button marginRight={2} onPress={onBackToPlan} theme={theme}>
|
|
69
|
+
{" back to plan "}
|
|
70
|
+
</Button>
|
|
71
|
+
<Button onPress={onAlways} theme={theme}>
|
|
72
|
+
{" always "}
|
|
73
|
+
</Button>
|
|
74
|
+
</Toolbar>
|
|
75
|
+
</box>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import { DEFAULT_QUICK_ACTIONS } from "../config";
|
|
4
|
+
import type { Story, StoryMeta } from "./story";
|
|
5
|
+
import { ConfirmCard } from "./ConfirmCard";
|
|
6
|
+
|
|
7
|
+
export const meta: StoryMeta = { title: "Cards/ConfirmCard" };
|
|
8
|
+
|
|
9
|
+
const callbacks = {
|
|
10
|
+
onInput: () => {},
|
|
11
|
+
onSelectMessage: () => {},
|
|
12
|
+
onSubmit: () => {},
|
|
13
|
+
onCancel: () => {},
|
|
14
|
+
quickActions: DEFAULT_QUICK_ACTIONS,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const ApproveDefault: Story = {
|
|
18
|
+
render: () => <ConfirmCard message="approved" summary="" {...callbacks} />,
|
|
19
|
+
expectedColors: [DARK.green, DARK.accent],
|
|
20
|
+
size: { width: 40, height: 14 },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const RequestChanges: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<ConfirmCard message="changes_requested" summary="Tighten the steps." {...callbacks} />
|
|
26
|
+
),
|
|
27
|
+
expectedColors: [DARK.red],
|
|
28
|
+
size: { width: 40, height: 14 },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const WithViewedSummary: Story = {
|
|
32
|
+
render: () => (
|
|
33
|
+
<ConfirmCard message="approved" summary="" viewedSummary="2/3 files viewed" {...callbacks} />
|
|
34
|
+
),
|
|
35
|
+
expectedColors: [DARK.green, DARK.textDim],
|
|
36
|
+
size: { width: 40, height: 14 },
|
|
37
|
+
};
|