@cueloop/client 0.1.0-alpha.10 → 0.1.0-alpha.101
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/annotations/annotation-palette.ts +62 -0
- package/src/annotations/components/AnnotationCards.stories.tsx +86 -0
- package/src/annotations/components/AnnotationCards.test.tsx +42 -0
- package/src/annotations/components/AnnotationCards.tsx +369 -0
- package/src/annotations/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/annotations/components/DiscussionMarkerRail.tsx +186 -0
- package/src/annotations/discussions.ts +77 -0
- package/src/annotations/thread-selection.test.ts +210 -0
- package/src/annotations/thread-selection.ts +242 -0
- package/src/annotations/use-annotation-surface.tsx +1243 -0
- package/src/app/App.appearance.test.tsx +53 -0
- package/src/{App.autoclose.test.tsx → app/App.autoclose.test.tsx} +77 -51
- package/src/app/App.deleted-scroll.test.tsx +187 -0
- package/src/app/App.diff.test.tsx +313 -0
- package/src/app/App.export.test.tsx +105 -0
- package/src/app/App.focus.test.ts +89 -0
- package/src/app/App.identity.test.tsx +84 -0
- package/src/app/App.plan-render.test.tsx +209 -0
- package/src/app/App.plan-review.test.tsx +428 -0
- package/src/app/App.prototype-markdown.test.tsx +128 -0
- package/src/app/App.prototype.test.tsx +176 -0
- package/src/app/App.readonly.test.tsx +210 -0
- package/src/app/App.regression.test.tsx +99 -0
- package/src/app/App.rendering.test.tsx +134 -0
- package/src/app/App.share-dialog.test.tsx +177 -0
- package/src/app/App.submit-confirm.test.tsx +267 -0
- package/src/app/App.test.tsx +496 -0
- package/src/app/App.thread-agent.test.ts +20 -0
- package/src/app/App.toast.test.tsx +124 -0
- package/src/app/App.tsx +1667 -0
- package/src/app/App.visual.test.tsx +59 -0
- package/src/app/App.walk.test.tsx +299 -0
- package/src/app/App.workbench.test.tsx +1149 -0
- package/src/app/app-key-state.ts +61 -0
- package/src/app/app-screens.tsx +606 -0
- package/src/app/app-view-model.ts +168 -0
- package/src/app/components/AppHeader.stories.tsx +50 -0
- package/src/app/components/AppHeader.tsx +81 -0
- package/src/app/components/AppShell.brand.test.tsx +58 -0
- package/src/app/components/AppShell.extensions.test.tsx +191 -0
- package/src/app/components/AppShell.stories.tsx +89 -0
- package/src/app/components/AppShell.tsx +358 -0
- package/src/app/components/AppShell.zoom.test.tsx +149 -0
- package/src/app/components/Breadcrumb.stories.tsx +19 -0
- package/src/app/components/Breadcrumb.tsx +45 -0
- package/src/app/components/MenuBar.stories.tsx +28 -0
- package/src/app/components/MenuBar.tsx +44 -0
- package/src/app/components/PaneGrid.stories.tsx +84 -0
- package/src/app/components/PaneGrid.tsx +128 -0
- package/src/app/components/PanelColumn.stories.tsx +25 -0
- package/src/app/components/PanelColumn.tsx +93 -0
- package/src/app/components/ShellHeader.stories.tsx +50 -0
- package/src/app/components/ShellHeader.tsx +137 -0
- package/src/app/components/WorkspacePanels.stories.tsx +36 -0
- package/src/app/components/WorkspacePanels.tsx +120 -0
- package/src/app/components/pages/Workspace.stories.tsx +181 -0
- package/src/app/intent-dispatch.test.ts +728 -0
- package/src/app/intent-dispatch.ts +687 -0
- package/src/app/launch-layout.test.ts +78 -0
- package/src/app/launch-layout.ts +57 -0
- package/src/app/ready-signal.test.ts +85 -0
- package/src/app/ready-signal.ts +52 -0
- package/src/app/run.ts +109 -0
- package/src/{serve.test.ts → app/serve.test.ts} +24 -12
- package/src/app/serve.ts +111 -0
- package/src/app/version.ts +5 -0
- package/src/appearance/components/theme-context.stories.tsx +46 -0
- package/src/appearance/components/theme-context.tsx +37 -0
- package/src/appearance/theme-presets.test.ts +51 -0
- package/src/appearance/theme-presets.ts +219 -0
- package/src/appearance/theme.ts +112 -0
- package/src/appearance/visual/contrast.test.ts +35 -0
- package/src/appearance/visual/contrast.ts +33 -0
- package/src/diff/components/AnnotatableFileView.stories.tsx +42 -0
- package/src/diff/components/AnnotatableFileView.test.tsx +157 -0
- package/src/diff/components/AnnotatableFileView.tsx +141 -0
- package/src/diff/components/BareWorkbenchFileView.stories.tsx +27 -0
- package/src/diff/components/BareWorkbenchFileView.tsx +63 -0
- package/src/diff/components/DiffContentView.scroll.test.tsx +434 -0
- package/src/diff/components/DiffContentView.split.test.tsx +140 -0
- package/src/diff/components/DiffContentView.stories.tsx +80 -0
- package/src/diff/components/DiffContentView.syntax.test.tsx +82 -0
- package/src/diff/components/DiffContentView.tsx +1055 -0
- package/src/diff/components/WalkWizard.stories.tsx +100 -0
- package/src/diff/components/WalkWizard.tsx +146 -0
- package/src/diff/components/diff-content-view-layout.test.ts +198 -0
- package/src/diff/components/diff-content-view-layout.ts +84 -0
- package/src/diff/diff-fold.test.ts +78 -0
- package/src/diff/diff-fold.ts +126 -0
- package/src/diff/diff-hunk-curate.test.ts +315 -0
- package/src/diff/diff-hunk-curate.ts +152 -0
- package/src/diff/diff-intraline.test.ts +239 -0
- package/src/diff/diff-intraline.ts +238 -0
- package/src/diff/diff-syntax.test.ts +125 -0
- package/src/diff/diff-syntax.ts +186 -0
- package/src/diff/split-diff.test.ts +110 -0
- package/src/diff/split-diff.ts +144 -0
- package/src/diff/view-diff.test.ts +325 -0
- package/src/diff/view-diff.ts +522 -0
- package/src/diff/walk.test.ts +121 -0
- package/src/diff/walk.ts +73 -0
- package/src/index.ts +31 -2
- package/src/integrations/attribution.test.ts +31 -0
- package/src/integrations/attribution.ts +42 -0
- package/src/integrations/client-extension-registry.test.tsx +61 -0
- package/src/integrations/client-extension-registry.ts +159 -0
- package/src/integrations/clipboard.test.ts +23 -0
- package/src/integrations/clipboard.ts +33 -0
- package/src/integrations/components/ExtensionZone.stories.tsx +28 -0
- package/src/integrations/components/ExtensionZone.tsx +145 -0
- package/src/integrations/components/ShareDialog.agent.test.tsx +50 -0
- package/src/integrations/components/ShareDialog.stories.tsx +121 -0
- package/src/integrations/components/ShareDialog.tsx +574 -0
- package/src/integrations/components/share-dialog-store.ts +59 -0
- package/src/integrations/editor.test.ts +149 -0
- package/src/integrations/editor.ts +160 -0
- package/src/integrations/github-identity.test.ts +58 -0
- package/src/integrations/github-identity.ts +41 -0
- package/src/integrations/herdr.test.ts +43 -0
- package/src/integrations/herdr.ts +38 -0
- package/src/integrations/integrations.test.ts +41 -0
- package/src/integrations/integrations.ts +32 -0
- package/src/integrations/pasted-image.test.ts +67 -0
- package/src/integrations/pasted-image.ts +60 -0
- package/src/integrations/share.ts +238 -0
- package/src/keyboard/components/CompletionOverlay.stories.tsx +37 -0
- package/src/keyboard/components/CompletionOverlay.tsx +75 -0
- package/src/keyboard/components/HandleChipsInput.stories.tsx +24 -0
- package/src/keyboard/components/HandleChipsInput.test.tsx +35 -0
- package/src/keyboard/components/HandleChipsInput.tsx +143 -0
- package/src/keyboard/components/KeybindsDialog.stories.tsx +31 -0
- package/src/keyboard/components/KeybindsDialog.tsx +103 -0
- package/src/keyboard/components/NavModeHint.stories.tsx +40 -0
- package/src/keyboard/components/NavModeHint.tsx +31 -0
- package/src/keyboard/components/SlashComposer.stories.tsx +37 -0
- package/src/keyboard/components/SlashComposer.tsx +87 -0
- package/src/keyboard/key-bindings.test.ts +156 -0
- package/src/keyboard/key-bindings.ts +459 -0
- package/src/keyboard/keymap.test.ts +611 -0
- package/src/keyboard/keymap.ts +395 -0
- package/src/keyboard/skills.test.ts +74 -0
- package/src/keyboard/skills.ts +75 -0
- package/src/keyboard/slash-palette.test.ts +103 -0
- package/src/keyboard/slash-palette.ts +129 -0
- package/src/keyboard/thread-chords.test.ts +79 -0
- package/src/keyboard/thread-chords.ts +163 -0
- package/src/keyboard/use-shared-keyboard.ts +55 -0
- package/src/markdown/__snapshots__/view-plan.regression.test.ts.snap +100 -0
- package/src/markdown/components/MarkdownGridBlock.stories.tsx +33 -0
- package/src/markdown/components/MarkdownGridBlock.tsx +72 -0
- package/src/markdown/components/MarkdownThreadEditor.stories.tsx +15 -0
- package/src/markdown/components/MarkdownThreadEditor.test.tsx +98 -0
- package/src/markdown/components/MarkdownThreadEditor.tsx +160 -0
- package/src/markdown/components/ThreadView.selection.test.tsx +951 -0
- package/src/markdown/components/ThreadView.slash.test.tsx +146 -0
- package/src/markdown/components/ThreadView.stories.tsx +26 -0
- package/src/markdown/components/ThreadView.tsx +560 -0
- package/src/markdown/mark-runs.ts +121 -0
- package/src/markdown/markdown-editor-syntax-style.ts +50 -0
- package/src/markdown/markdown-grid.test.ts +101 -0
- package/src/markdown/markdown-grid.ts +203 -0
- package/src/markdown/markdown-highlight.test.ts +61 -0
- package/src/markdown/markdown-highlight.ts +124 -0
- package/src/markdown/thread-code-syntax.ts +45 -0
- package/src/markdown/use-thread-body-editing.tsx +54 -0
- package/src/markdown/view-plan.regression.test.ts +60 -0
- package/src/markdown/view-plan.test.ts +545 -0
- package/src/markdown/view-plan.ts +1009 -0
- package/src/performance/otlp-trace.test.ts +119 -0
- package/src/performance/otlp-trace.ts +134 -0
- package/src/performance/perf-report.ts +18 -0
- package/src/performance/perf-timings.test.ts +18 -0
- package/src/performance/perf-timings.ts +39 -0
- package/src/prototype/components/PrototypeContentView.stories.tsx +19 -0
- package/src/prototype/components/PrototypeContentView.tsx +442 -0
- package/src/prototype/prototype-browser.test.ts +81 -0
- package/src/prototype/prototype-browser.ts +321 -0
- package/src/prototype/prototype-pixels.tsx +35 -0
- package/src/settings/components/SettingsDialog.stories.tsx +117 -0
- package/src/settings/components/SettingsDialog.tsx +183 -0
- package/src/settings/components/SettingsRows.stories.tsx +18 -0
- package/src/settings/components/SettingsRows.tsx +112 -0
- package/src/settings/components/quick-actions-editor.stories.tsx +58 -0
- package/src/settings/components/quick-actions-editor.test.tsx +120 -0
- package/src/settings/components/quick-actions-editor.tsx +222 -0
- package/src/settings/config-identity.test.ts +78 -0
- package/src/settings/config.test.ts +676 -0
- package/src/settings/config.ts +628 -0
- package/src/settings/use-settings-dialog.tsx +351 -0
- package/src/stories/__snapshots__/stories.test.tsx.snap +2358 -0
- package/src/stories/stories-app.tsx +196 -0
- package/src/stories/stories.test.tsx +74 -0
- package/src/stories/story-fixtures.ts +102 -0
- package/src/stories/story-tree.test.ts +108 -0
- package/src/stories/story.ts +154 -0
- package/src/terminal/ansi-screen.test.ts +28 -0
- package/src/terminal/ansi-screen.ts +72 -0
- package/src/terminal/components/AnsiScreen.stories.tsx +12 -0
- package/src/terminal/components/AnsiScreen.tsx +28 -0
- package/src/terminal/components/terminal-pane.test.tsx +40 -0
- package/src/terminal/components/terminal-pane.ts +196 -0
- package/src/terminal/ghostty-terminal.test.ts +85 -0
- package/src/terminal/ghostty-terminal.ts +197 -0
- package/src/terminal/kitty-image.test.ts +66 -0
- package/src/terminal/kitty-image.ts +137 -0
- package/src/terminal/pty.test.ts +81 -0
- package/src/terminal/pty.ts +246 -0
- package/src/testing/test-support.test.ts +264 -0
- package/src/testing/test-support.ts +367 -0
- package/src/thread/agent-continuation.test.ts +32 -0
- package/src/thread/agent-continuation.ts +13 -0
- package/src/thread/agent-transcript.test.ts +161 -0
- package/src/thread/agent-transcript.ts +336 -0
- package/src/thread/components/AgentConfigControls.stories.tsx +39 -0
- package/src/thread/components/AgentConfigControls.test.tsx +112 -0
- package/src/thread/components/AgentConfigControls.tsx +114 -0
- package/src/thread/components/AgentThreadPane.stories.tsx +105 -0
- package/src/thread/components/AgentThreadPane.test.tsx +935 -0
- package/src/thread/components/AgentThreadPane.tsx +377 -0
- package/src/thread/components/ThreadFooter.stories.tsx +30 -0
- package/src/thread/components/ThreadFooter.test.tsx +49 -0
- package/src/thread/components/ThreadFooter.tsx +77 -0
- package/src/thread/components/ThreadTree.stories.tsx +46 -0
- package/src/thread/components/ThreadTree.test.tsx +41 -0
- package/src/thread/components/ThreadTree.tsx +267 -0
- package/src/thread/components/thread-tree.test.ts +137 -0
- package/src/thread/components/thread-tree.ts +112 -0
- package/src/thread/thread-controller.curate.test.ts +502 -0
- package/src/thread/thread-controller.derivation.test.ts +62 -0
- package/src/thread/thread-controller.live-diff.test.ts +357 -0
- package/src/thread/thread-controller.optimistic.test.ts +69 -0
- package/src/thread/thread-controller.share.test.ts +573 -0
- package/src/thread/thread-controller.switch-cache.test.ts +174 -0
- package/src/thread/thread-controller.tree.test.ts +263 -0
- package/src/thread/thread-controller.ts +2026 -0
- package/src/thread/tree-view.test.ts +128 -0
- package/src/thread/tree-view.ts +146 -0
- package/src/thread/use-thread-agent.test.tsx +208 -0
- package/src/thread/use-thread-agent.ts +117 -0
- package/src/ui/components/CodeBlock.stories.tsx +42 -0
- package/src/ui/components/CodeBlock.tsx +80 -0
- package/src/ui/components/ConfirmCard.stories.tsx +37 -0
- package/src/ui/components/ConfirmCard.tsx +151 -0
- package/src/ui/components/ConfirmDialog.stories.tsx +21 -0
- package/src/ui/components/ConfirmDialog.tsx +72 -0
- package/src/ui/components/OverlayScrollbar.stories.tsx +33 -0
- package/src/ui/components/OverlayScrollbar.test.tsx +139 -0
- package/src/ui/components/OverlayScrollbar.tsx +129 -0
- package/src/ui/components/PromptDialog.stories.tsx +36 -0
- package/src/ui/components/PromptDialog.test.tsx +65 -0
- package/src/ui/components/PromptDialog.tsx +99 -0
- package/src/ui/components/RootOverlay.stories.tsx +45 -0
- package/src/ui/components/RootOverlay.tsx +49 -0
- package/src/ui/components/ScrollArea.stories.tsx +20 -0
- package/src/ui/components/ScrollArea.tsx +61 -0
- package/src/ui/components/Toast.stories.tsx +19 -0
- package/src/ui/components/Toast.tsx +54 -0
- package/src/ui/components/Tooltip.stories.tsx +26 -0
- package/src/ui/components/Tooltip.tsx +72 -0
- package/src/ui/components/menu-control.stories.tsx +35 -0
- package/src/ui/components/menu-control.tsx +43 -0
- package/src/ui/components/primitives/Button.stories.tsx +32 -0
- package/src/ui/components/primitives/Button.tsx +57 -0
- package/src/ui/components/primitives/Card.stories.tsx +25 -0
- package/src/ui/components/primitives/Card.tsx +62 -0
- package/src/ui/components/primitives/Dialog.stories.tsx +24 -0
- package/src/ui/components/primitives/Dialog.test.tsx +38 -0
- package/src/ui/components/primitives/Dialog.tsx +69 -0
- package/src/ui/components/primitives/DialogActions.stories.tsx +20 -0
- package/src/ui/components/primitives/DialogActions.tsx +39 -0
- package/src/ui/components/primitives/IconButton.stories.tsx +36 -0
- package/src/ui/components/primitives/IconButton.tsx +58 -0
- package/src/ui/components/primitives/StatusBar.stories.tsx +11 -0
- package/src/ui/components/primitives/StatusBar.tsx +23 -0
- package/src/ui/components/primitives/Switch.stories.tsx +17 -0
- package/src/ui/components/primitives/Switch.tsx +32 -0
- package/src/ui/components/primitives/Tabs.stories.tsx +29 -0
- package/src/ui/components/primitives/Tabs.tsx +101 -0
- package/src/ui/components/primitives/Toolbar.stories.tsx +17 -0
- package/src/ui/components/primitives/Toolbar.tsx +14 -0
- package/src/ui/components/primitives/Tree.stories.tsx +124 -0
- package/src/ui/components/primitives/Tree.tsx +133 -0
- package/src/ui/components/primitives/frame.test.ts +12 -0
- package/src/ui/components/primitives/frame.ts +17 -0
- package/src/ui/components/primitives/icons.ts +61 -0
- package/src/ui/components/primitives/tree-model.test.ts +99 -0
- package/src/ui/components/primitives/tree-model.ts +137 -0
- package/src/ui/components/syntax-highlight.ts +133 -0
- package/src/ui/components/truncate-text.ts +7 -0
- package/src/ui/components/truncate-title.test.ts +31 -0
- package/src/ui/components/truncate-title.ts +15 -0
- package/src/ui/use-frame-measure.ts +74 -0
- package/src/ui/use-terminal-virtualizer.ts +259 -0
- package/src/workbench/components/ChangesColumn.stories.tsx +44 -0
- package/src/workbench/components/ChangesColumn.tsx +203 -0
- package/src/workbench/components/ChangesFileTree.test.tsx +181 -0
- package/src/workbench/components/EditorGrid.split.test.tsx +118 -0
- package/src/workbench/components/EditorGrid.stories.tsx +86 -0
- package/src/workbench/components/EditorGrid.tabs.test.tsx +118 -0
- package/src/workbench/components/EditorGrid.tsx +544 -0
- package/src/workbench/components/EditorGrid.zoom.test.tsx +132 -0
- package/src/workbench/components/FileContentsView.stories.tsx +16 -0
- package/src/workbench/components/FileContentsView.test.tsx +29 -0
- package/src/workbench/components/FileContentsView.tsx +100 -0
- package/src/workbench/components/GridTabContent.actions.test.tsx +151 -0
- package/src/workbench/components/GridTabContent.stories.tsx +59 -0
- package/src/workbench/components/GridTabContent.tsx +193 -0
- package/src/workbench/components/ProjectTreeView.stories.tsx +25 -0
- package/src/workbench/components/ProjectTreeView.test.tsx +236 -0
- package/src/workbench/components/ProjectTreeView.tsx +151 -0
- package/src/workbench/components/TreePane.stories.tsx +106 -0
- package/src/workbench/components/TreePane.tsx +155 -0
- package/src/workbench/components/WelcomePlayground.stories.tsx +12 -0
- package/src/workbench/components/WelcomePlayground.test.tsx +83 -0
- package/src/workbench/components/WelcomePlayground.tsx +138 -0
- package/src/workbench/components/editor-grid.test.ts +184 -0
- package/src/workbench/components/editor-grid.ts +281 -0
- package/src/workbench/components/file-tree.test.ts +60 -0
- package/src/workbench/components/file-tree.ts +112 -0
- package/src/workbench/use-changes-workbench.ts +205 -0
- package/src/workbench/use-remember-layout.ts +31 -0
- package/src/workbench/use-repo-changes.ts +91 -0
- package/src/workbench/workbench-snapshot.test.ts +99 -0
- package/src/workbench/workbench-snapshot.ts +59 -0
- package/src/App.diff.test.tsx +0 -99
- package/src/App.export.test.tsx +0 -88
- package/src/App.plan-review.test.tsx +0 -259
- package/src/App.readonly.test.tsx +0 -148
- package/src/App.rendering.test.tsx +0 -108
- package/src/App.submit-confirm.test.tsx +0 -183
- package/src/App.test.tsx +0 -218
- package/src/App.tsx +0 -1249
- package/src/config.test.ts +0 -116
- package/src/config.ts +0 -150
- package/src/editor.ts +0 -34
- package/src/herdr.test.ts +0 -68
- package/src/herdr.ts +0 -34
- package/src/keymap.test.ts +0 -270
- package/src/keymap.ts +0 -173
- package/src/run.ts +0 -30
- package/src/serve.ts +0 -75
- package/src/session-controller.ts +0 -398
- package/src/syntax.test.ts +0 -41
- package/src/syntax.ts +0 -88
- package/src/theme.ts +0 -46
- package/src/view-diff.test.ts +0 -55
- package/src/view-diff.ts +0 -86
- package/src/view.test.ts +0 -157
- package/src/view.ts +0 -364
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../../appearance/theme";
|
|
3
|
+
import type { WalkFile } from "../walk";
|
|
4
|
+
import type { Story, StoryMeta } from "../../stories/story";
|
|
5
|
+
import { WalkWizard } from "./WalkWizard";
|
|
6
|
+
|
|
7
|
+
export const meta: StoryMeta = { title: "Overlays/WalkWizard" };
|
|
8
|
+
|
|
9
|
+
const FILES: WalkFile[] = [
|
|
10
|
+
{
|
|
11
|
+
path: "packages/daemon/src/store.ts",
|
|
12
|
+
added: 12,
|
|
13
|
+
removed: 3,
|
|
14
|
+
preview: [
|
|
15
|
+
{ sign: "+", text: "const viewed = new Set<string>();" },
|
|
16
|
+
{ sign: "+", text: "record.viewedPaths = [...viewed];" },
|
|
17
|
+
{ sign: "-", text: "// viewed tracking lives in the client only" },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
path: "packages/client/src/walk.ts",
|
|
22
|
+
added: 48,
|
|
23
|
+
removed: 0,
|
|
24
|
+
preview: [{ sign: "+", text: "export function firstUnviewedIndex(files, viewed) {" }],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
path: "packages/schema/src/types.ts",
|
|
28
|
+
added: 3,
|
|
29
|
+
removed: 0,
|
|
30
|
+
preview: [{ sign: "+", text: "viewedPaths?: string[];" }],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const noop = (): void => {};
|
|
35
|
+
|
|
36
|
+
export const FirstFile: Story = {
|
|
37
|
+
render: () => (
|
|
38
|
+
<WalkWizard
|
|
39
|
+
files={FILES}
|
|
40
|
+
index={0}
|
|
41
|
+
viewedPaths={new Set()}
|
|
42
|
+
terminalWidth={80}
|
|
43
|
+
onSubmitRequest={noop}
|
|
44
|
+
onBack={noop}
|
|
45
|
+
/>
|
|
46
|
+
),
|
|
47
|
+
// the main card wears the accent border; stats carry the +/- colors
|
|
48
|
+
expectedColors: [
|
|
49
|
+
DARK.accent,
|
|
50
|
+
DARK.green,
|
|
51
|
+
DARK.red,
|
|
52
|
+
DARK.insertedForeground,
|
|
53
|
+
DARK.deletedForeground,
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const WithAgentNote: Story = {
|
|
58
|
+
render: () => (
|
|
59
|
+
<WalkWizard
|
|
60
|
+
files={FILES}
|
|
61
|
+
index={1}
|
|
62
|
+
viewedPaths={new Set(["packages/daemon/src/store.ts"])}
|
|
63
|
+
note="New module: pure helpers that pick the next unviewed file. No IO, unit tested."
|
|
64
|
+
terminalWidth={80}
|
|
65
|
+
onSubmitRequest={noop}
|
|
66
|
+
onBack={noop}
|
|
67
|
+
/>
|
|
68
|
+
),
|
|
69
|
+
// the agent-note block wears the plain gray border token, not the accent
|
|
70
|
+
expectedColors: [DARK.accent, DARK.border, DARK.textMuted],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const EndCard: Story = {
|
|
74
|
+
render: () => (
|
|
75
|
+
<WalkWizard
|
|
76
|
+
files={FILES}
|
|
77
|
+
index={3}
|
|
78
|
+
viewedPaths={new Set(FILES.map((file) => file.path))}
|
|
79
|
+
terminalWidth={80}
|
|
80
|
+
onSubmitRequest={noop}
|
|
81
|
+
onBack={noop}
|
|
82
|
+
/>
|
|
83
|
+
),
|
|
84
|
+
// accent border and solid submit; the all-viewed line turns green
|
|
85
|
+
expectedColors: [DARK.green, DARK.accent, DARK.accentInk],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const EndCardPartialPass: Story = {
|
|
89
|
+
render: () => (
|
|
90
|
+
<WalkWizard
|
|
91
|
+
files={FILES}
|
|
92
|
+
index={3}
|
|
93
|
+
viewedPaths={new Set(["packages/daemon/src/store.ts"])}
|
|
94
|
+
terminalWidth={80}
|
|
95
|
+
onSubmitRequest={noop}
|
|
96
|
+
onBack={noop}
|
|
97
|
+
/>
|
|
98
|
+
),
|
|
99
|
+
expectedColors: [DARK.accent, DARK.accentInk],
|
|
100
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guided walk's focused card wizard: one card per changed file
|
|
3
|
+
* floating over the dimmed diff, with a plain step count in the title - no
|
|
4
|
+
* progress bar. Below the main card a separate gray-bordered block carries
|
|
5
|
+
* the submitting agent's note for the file, when one exists. Past the last
|
|
6
|
+
* file the end card offers the submit action directly with honest counts.
|
|
7
|
+
* The keys live in the keymap's walk overlay; this component only renders.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from "react";
|
|
11
|
+
import type { Theme } from "../../appearance/theme";
|
|
12
|
+
import { useComponentTheme } from "../../appearance/components/theme-context";
|
|
13
|
+
import type { WalkFile } from "../walk";
|
|
14
|
+
import { viewedCount } from "../walk";
|
|
15
|
+
import { Card } from "../../ui/components/primitives/Card";
|
|
16
|
+
import { DialogActions } from "../../ui/components/primitives/DialogActions";
|
|
17
|
+
|
|
18
|
+
export interface WalkWizardProps {
|
|
19
|
+
files: WalkFile[];
|
|
20
|
+
index: number;
|
|
21
|
+
viewedPaths: ReadonlySet<string>;
|
|
22
|
+
note?: string;
|
|
23
|
+
terminalWidth: number;
|
|
24
|
+
onSubmitRequest: () => void;
|
|
25
|
+
onBack: () => void;
|
|
26
|
+
theme?: Theme;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The card never crowds the terminal edge nor stretches past reading width. */
|
|
30
|
+
function wizardWidth(terminalWidth: number): number {
|
|
31
|
+
return Math.min(72, Math.max(24, terminalWidth - 8));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Note rows from a crude wrap estimate, capped so the block stays a block. */
|
|
35
|
+
function noteRows(note: string, innerWidth: number): number {
|
|
36
|
+
return Math.min(3, Math.max(1, Math.ceil(note.length / Math.max(1, innerWidth))));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function WalkWizard({
|
|
40
|
+
files,
|
|
41
|
+
index,
|
|
42
|
+
viewedPaths,
|
|
43
|
+
note,
|
|
44
|
+
terminalWidth,
|
|
45
|
+
onSubmitRequest,
|
|
46
|
+
onBack,
|
|
47
|
+
theme,
|
|
48
|
+
}: WalkWizardProps): React.ReactNode {
|
|
49
|
+
const tokens = useComponentTheme(theme);
|
|
50
|
+
const width = wizardWidth(terminalWidth);
|
|
51
|
+
const viewed = viewedCount(files, viewedPaths);
|
|
52
|
+
const atEnd = index >= files.length;
|
|
53
|
+
const currentFile = files[index];
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<box
|
|
57
|
+
style={{
|
|
58
|
+
position: "absolute",
|
|
59
|
+
left: 0,
|
|
60
|
+
top: 0,
|
|
61
|
+
width: "100%",
|
|
62
|
+
height: "100%",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
justifyContent: "center",
|
|
65
|
+
flexDirection: "column",
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<box style={{ width, flexDirection: "column" }}>
|
|
69
|
+
{atEnd || !currentFile ? (
|
|
70
|
+
<Card
|
|
71
|
+
title=" walk complete "
|
|
72
|
+
contentRows={3}
|
|
73
|
+
borderColor={tokens.accent}
|
|
74
|
+
backgroundColor={tokens.elevated}
|
|
75
|
+
theme={theme}
|
|
76
|
+
>
|
|
77
|
+
<text fg={viewed === files.length ? tokens.green : tokens.text}>
|
|
78
|
+
{viewed === files.length
|
|
79
|
+
? `every file viewed (${viewed}/${files.length})`
|
|
80
|
+
: `${viewed} of ${files.length} files viewed`}
|
|
81
|
+
</text>
|
|
82
|
+
<box style={{ height: 1 }} />
|
|
83
|
+
<DialogActions
|
|
84
|
+
confirmLabel="submit"
|
|
85
|
+
cancelLabel="back"
|
|
86
|
+
onConfirm={onSubmitRequest}
|
|
87
|
+
onCancel={onBack}
|
|
88
|
+
theme={theme}
|
|
89
|
+
/>
|
|
90
|
+
</Card>
|
|
91
|
+
) : (
|
|
92
|
+
<>
|
|
93
|
+
<Card
|
|
94
|
+
title={` file ${index + 1} of ${files.length} · ${viewed} viewed `}
|
|
95
|
+
contentRows={3 + Math.max(1, currentFile.preview.length)}
|
|
96
|
+
borderColor={tokens.accent}
|
|
97
|
+
backgroundColor={tokens.elevated}
|
|
98
|
+
theme={theme}
|
|
99
|
+
>
|
|
100
|
+
<text fg={tokens.text}>{currentFile.path}</text>
|
|
101
|
+
<text>
|
|
102
|
+
<span fg={tokens.green}>{`+${currentFile.added}`}</span>
|
|
103
|
+
<span fg={tokens.textDim}> </span>
|
|
104
|
+
<span fg={tokens.red}>{`-${currentFile.removed}`}</span>
|
|
105
|
+
<span fg={tokens.textDim}>
|
|
106
|
+
{viewedPaths.has(currentFile.path) ? " · viewed" : ""}
|
|
107
|
+
</span>
|
|
108
|
+
</text>
|
|
109
|
+
<box style={{ height: 1 }} />
|
|
110
|
+
{currentFile.preview.length === 0 ? (
|
|
111
|
+
<text fg={tokens.textDim}>(no changed lines)</text>
|
|
112
|
+
) : (
|
|
113
|
+
currentFile.preview.map((previewLine, previewIndex) => (
|
|
114
|
+
<text
|
|
115
|
+
key={previewIndex}
|
|
116
|
+
style={{ wrapMode: "none" }}
|
|
117
|
+
fg={
|
|
118
|
+
previewLine.sign === "+"
|
|
119
|
+
? tokens.insertedForeground
|
|
120
|
+
: tokens.deletedForeground
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
{previewLine.sign + previewLine.text}
|
|
124
|
+
</text>
|
|
125
|
+
))
|
|
126
|
+
)}
|
|
127
|
+
</Card>
|
|
128
|
+
{note !== undefined ? (
|
|
129
|
+
<box style={{ marginTop: 1, flexDirection: "column" }}>
|
|
130
|
+
<Card
|
|
131
|
+
title=" agent note "
|
|
132
|
+
contentRows={noteRows(note, width - 4)}
|
|
133
|
+
borderColor={tokens.border}
|
|
134
|
+
backgroundColor={tokens.elevated}
|
|
135
|
+
theme={theme}
|
|
136
|
+
>
|
|
137
|
+
<text fg={tokens.textMuted}>{note}</text>
|
|
138
|
+
</Card>
|
|
139
|
+
</box>
|
|
140
|
+
) : null}
|
|
141
|
+
</>
|
|
142
|
+
)}
|
|
143
|
+
</box>
|
|
144
|
+
</box>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { Annotation } from "@cueloop/schema";
|
|
3
|
+
import {
|
|
4
|
+
commentCountsByFile,
|
|
5
|
+
diffRowText,
|
|
6
|
+
fileChangeCounts,
|
|
7
|
+
marksByRows,
|
|
8
|
+
type DiffRow,
|
|
9
|
+
} from "../view-diff";
|
|
10
|
+
import { fixtureDiffSession } from "../../stories/story-fixtures";
|
|
11
|
+
import type { IntralineRun } from "../diff-intraline";
|
|
12
|
+
import type { SyntaxSpan } from "../diff-syntax";
|
|
13
|
+
import { DARK } from "../../appearance/theme";
|
|
14
|
+
import { coloredRowSpans } from "./diff-content-view-layout";
|
|
15
|
+
|
|
16
|
+
function row(kind: DiffRow["kind"], text: string, extra: Partial<DiffRow> = {}): DiffRow {
|
|
17
|
+
return { kind, text, file: "a.ts", ...extra };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function annotation(id: string, anchor: Annotation["anchor"]): Annotation {
|
|
21
|
+
return { id, kind: "comment", body: "note", anchor, createdAt: "2026-01-01T00:00:00Z" };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("diffRowText", () => {
|
|
25
|
+
test("strips the trailing newline the patch carries", () => {
|
|
26
|
+
// Arrange / Act / Assert
|
|
27
|
+
expect(diffRowText(row("add", "const x = 1;\n"))).toBe("const x = 1;");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("fileChangeCounts", () => {
|
|
32
|
+
test("tallies added and removed lines per file, ignoring context and headers", () => {
|
|
33
|
+
// Arrange
|
|
34
|
+
const rows = [
|
|
35
|
+
row("file", "a.ts"),
|
|
36
|
+
row("hunk", "@@"),
|
|
37
|
+
row("ctx", "unchanged"),
|
|
38
|
+
row("del", "gone"),
|
|
39
|
+
row("add", "one"),
|
|
40
|
+
row("add", "two"),
|
|
41
|
+
row("file", "b.ts", { file: "b.ts" }),
|
|
42
|
+
row("add", "only add", { file: "b.ts" }),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// Act
|
|
46
|
+
const counts = fileChangeCounts(rows);
|
|
47
|
+
|
|
48
|
+
// Assert
|
|
49
|
+
expect(counts.get("a.ts")).toEqual({ additions: 2, deletions: 1 });
|
|
50
|
+
expect(counts.get("b.ts")).toEqual({ additions: 1, deletions: 0 });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("marksByRows", () => {
|
|
55
|
+
const rows = [
|
|
56
|
+
row("file", "a.ts"),
|
|
57
|
+
row("hunk", "@@"),
|
|
58
|
+
row("ctx", "keep\n"),
|
|
59
|
+
row("add", "const items = new Map();\n"),
|
|
60
|
+
row("add", "return items;\n"),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
test("a whole-row quote (the legacy row anchor) marks that row end to end", () => {
|
|
64
|
+
// Act
|
|
65
|
+
const marks = marksByRows(
|
|
66
|
+
[annotation("a1", { quote: "return items;", prefix: "", suffix: "" })],
|
|
67
|
+
rows,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Assert
|
|
71
|
+
expect([...marks.keys()]).toEqual([4]);
|
|
72
|
+
expect(marks.get(4)![0]).toMatchObject({ start: 0, end: 13, annotationId: "a1" });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("a word quote marks only its characters and carries the span", () => {
|
|
76
|
+
// Act
|
|
77
|
+
const marks = marksByRows(
|
|
78
|
+
[annotation("a1", { quote: "new Map()", prefix: "const items = ", suffix: ";" })],
|
|
79
|
+
rows,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Assert
|
|
83
|
+
const mark = marks.get(3)![0]!;
|
|
84
|
+
|
|
85
|
+
expect(mark.start).toBe(14);
|
|
86
|
+
expect(mark.end).toBe(23);
|
|
87
|
+
expect(mark.span).toEqual({
|
|
88
|
+
start: { blockIndex: 3, char: 14 },
|
|
89
|
+
end: { blockIndex: 3, char: 23 },
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("a quote across two rows paints a mark on each row with one shared span", () => {
|
|
94
|
+
// Arrange - the anchor a two-row drag produces: rows joined by the block separator
|
|
95
|
+
const marks = marksByRows(
|
|
96
|
+
[
|
|
97
|
+
annotation("a1", {
|
|
98
|
+
quote: "Map();\n\nreturn",
|
|
99
|
+
prefix: "const items = new ",
|
|
100
|
+
suffix: " items;",
|
|
101
|
+
blockIndex: 3,
|
|
102
|
+
start: 18,
|
|
103
|
+
end: 6,
|
|
104
|
+
endBlockIndex: 4,
|
|
105
|
+
}),
|
|
106
|
+
],
|
|
107
|
+
rows,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Assert
|
|
111
|
+
expect([...marks.keys()].toSorted()).toEqual([3, 4]);
|
|
112
|
+
expect(marks.get(3)![0]!.span).toEqual(marks.get(4)![0]!.span);
|
|
113
|
+
expect(marks.get(3)![0]).toMatchObject({ start: 18, end: 24 });
|
|
114
|
+
expect(marks.get(4)![0]).toMatchObject({ start: 0, end: 6 });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("the focused annotation wears the focus role", () => {
|
|
118
|
+
// Act
|
|
119
|
+
const marks = marksByRows(
|
|
120
|
+
[annotation("a1", { quote: "keep", prefix: "", suffix: "" })],
|
|
121
|
+
rows,
|
|
122
|
+
"a1",
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Assert
|
|
126
|
+
expect(marks.get(2)![0]!.role).toBe("mark-focus");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("commentCountsByFile", () => {
|
|
131
|
+
const rows = [
|
|
132
|
+
row("file", "a.ts"),
|
|
133
|
+
row("add", "const items = new Map();\n"),
|
|
134
|
+
row("file", "b.ts", { file: "b.ts" }),
|
|
135
|
+
row("add", "return other;\n", { file: "b.ts" }),
|
|
136
|
+
];
|
|
137
|
+
const annotation = (id: string, quote: string, replyTo?: string): Annotation => {
|
|
138
|
+
const comment: Annotation = {
|
|
139
|
+
id,
|
|
140
|
+
kind: "comment",
|
|
141
|
+
body: id,
|
|
142
|
+
anchor: { quote, prefix: "", suffix: "" },
|
|
143
|
+
createdAt: "2026-01-01T00:00:00Z",
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (replyTo !== undefined) comment.replyTo = replyTo;
|
|
147
|
+
|
|
148
|
+
return comment;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
test("counts a discussion's comments (root plus replies) toward its file", () => {
|
|
152
|
+
// Arrange - one thread on a.ts with a reply, one on b.ts
|
|
153
|
+
const session = fixtureDiffSession({
|
|
154
|
+
annotations: [
|
|
155
|
+
annotation("a1", "const items = new Map();"),
|
|
156
|
+
annotation("a1r", "const items = new Map();", "a1"),
|
|
157
|
+
annotation("b1", "return other;"),
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Act
|
|
162
|
+
const counts = commentCountsByFile(session, rows);
|
|
163
|
+
|
|
164
|
+
// Assert
|
|
165
|
+
expect(counts.get("a.ts")).toBe(2);
|
|
166
|
+
expect(counts.get("b.ts")).toBe(1);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("coloredRowSpans", () => {
|
|
171
|
+
test("a changed word keeps the diff color while unchanged text dims", () => {
|
|
172
|
+
// Arrange - "old" is the change, " tail" unchanged, on a modified line
|
|
173
|
+
const runs: IntralineRun[] = [
|
|
174
|
+
{ text: "old", changed: true },
|
|
175
|
+
{ text: " tail", changed: false },
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
// Act
|
|
179
|
+
const spans = coloredRowSpans("old tail", runs, undefined, DARK.deletedForeground, DARK);
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(spans).toEqual([
|
|
183
|
+
{ text: "old", foreground: DARK.deletedForeground },
|
|
184
|
+
{ text: " tail", foreground: DARK.textDim },
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("syntax color applies to unchanged text when there is no intra-line run", () => {
|
|
189
|
+
// Arrange - a keyword span over the whole word, no intra-line runs
|
|
190
|
+
const syntax: SyntaxSpan[] = [{ start: 0, end: 6, group: "keyword" }];
|
|
191
|
+
|
|
192
|
+
// Act
|
|
193
|
+
const spans = coloredRowSpans("return", undefined, syntax, DARK.textMuted, DARK);
|
|
194
|
+
|
|
195
|
+
// Assert
|
|
196
|
+
expect(spans).toEqual([{ text: "return", foreground: DARK.accent }]);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure color logic for the diff review sheet, split out so it can be tested
|
|
3
|
+
* without rendering: a row's colored spans (syntax under the intra-line change
|
|
4
|
+
* color). DiffContentView.tsx paints marks and the caret over these per character.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Theme } from "../../appearance/theme";
|
|
8
|
+
import type { IntralineRun } from "../diff-intraline";
|
|
9
|
+
import type { SyntaxSpan } from "../diff-syntax";
|
|
10
|
+
import { colorForSyntaxGroup } from "../../ui/components/syntax-highlight";
|
|
11
|
+
|
|
12
|
+
/** One rendered span of a row: its text and its resolved foreground color. */
|
|
13
|
+
export interface ColoredSpan {
|
|
14
|
+
text: string;
|
|
15
|
+
foreground: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Colored spans for one row, resolved per character: an intra-line changed word
|
|
20
|
+
* keeps the diff color, otherwise the syntax color applies, falling back to the
|
|
21
|
+
* dim token on the unchanged part of a modified line or the row's base color.
|
|
22
|
+
* Adjacent same-color characters coalesce so a row stays a few spans.
|
|
23
|
+
*/
|
|
24
|
+
export function coloredRowSpans(
|
|
25
|
+
text: string,
|
|
26
|
+
intralineRuns: IntralineRun[] | undefined,
|
|
27
|
+
syntaxSpans: SyntaxSpan[] | undefined,
|
|
28
|
+
baseColor: string,
|
|
29
|
+
tokens: Theme,
|
|
30
|
+
): ColoredSpan[] {
|
|
31
|
+
const changed = changedColumns(text.length, intralineRuns);
|
|
32
|
+
const syntaxColorByColumn = syntaxColorColumns(text.length, syntaxSpans, tokens);
|
|
33
|
+
const isModifiedLine = intralineRuns !== undefined;
|
|
34
|
+
|
|
35
|
+
const spans: ColoredSpan[] = [];
|
|
36
|
+
|
|
37
|
+
for (let column = 0; column < text.length; column++) {
|
|
38
|
+
const color = changed[column]
|
|
39
|
+
? baseColor
|
|
40
|
+
: (syntaxColorByColumn[column] ?? (isModifiedLine ? tokens.textDim : baseColor));
|
|
41
|
+
const previous = spans[spans.length - 1];
|
|
42
|
+
|
|
43
|
+
if (previous && previous.foreground === color) previous.text += text[column];
|
|
44
|
+
else spans.push({ text: text[column]!, foreground: color });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return spans;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Which columns are the intra-line change on a modified line. */
|
|
51
|
+
function changedColumns(length: number, intralineRuns: IntralineRun[] | undefined): boolean[] {
|
|
52
|
+
const changed = Array.from({ length }, () => false);
|
|
53
|
+
|
|
54
|
+
if (!intralineRuns) return changed;
|
|
55
|
+
let offset = 0;
|
|
56
|
+
|
|
57
|
+
for (const run of intralineRuns) {
|
|
58
|
+
if (run.changed)
|
|
59
|
+
for (let index = 0; index < run.text.length; index++) changed[offset + index] = true;
|
|
60
|
+
offset += run.text.length;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return changed;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The syntax color per column, or undefined where a column has no token. */
|
|
67
|
+
function syntaxColorColumns(
|
|
68
|
+
length: number,
|
|
69
|
+
syntaxSpans: SyntaxSpan[] | undefined,
|
|
70
|
+
tokens: Theme,
|
|
71
|
+
): Array<string | undefined> {
|
|
72
|
+
const colors: Array<string | undefined> = Array.from({ length }, () => undefined);
|
|
73
|
+
|
|
74
|
+
for (const span of syntaxSpans ?? []) {
|
|
75
|
+
const color = colorForSyntaxGroup(span.group, tokens);
|
|
76
|
+
|
|
77
|
+
if (!color) continue;
|
|
78
|
+
for (let column = span.start; column < span.end && column < length; column++) {
|
|
79
|
+
colors[column] = color;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return colors;
|
|
84
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Per-file collapse and full-file weave over the flat diff rows. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from "bun:test";
|
|
4
|
+
import type { DiffRow } from "./view-diff";
|
|
5
|
+
import { applyFold, weaveFullFileRows } from "./diff-fold";
|
|
6
|
+
|
|
7
|
+
function row(kind: DiffRow["kind"], text: string, extra: Partial<DiffRow> = {}): DiffRow {
|
|
8
|
+
return { kind, text, file: "a.ts", ...extra };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// a two-line change in a four-line file: line 2 swapped, lines 1/3/4 unchanged
|
|
12
|
+
const base: DiffRow[] = [
|
|
13
|
+
row("file", "a.ts"),
|
|
14
|
+
row("hunk", "@@ -1,3 +1,3 @@"),
|
|
15
|
+
row("ctx", "line one", { oldLine: 1, newLine: 1 }),
|
|
16
|
+
row("del", "old two", { oldLine: 2 }),
|
|
17
|
+
row("add", "new two", { newLine: 2 }),
|
|
18
|
+
row("ctx", "line three", { oldLine: 3, newLine: 3 }),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const contents = {
|
|
22
|
+
path: "a.ts",
|
|
23
|
+
oldContents: "line one\nold two\nline three\nline four\n",
|
|
24
|
+
newContents: "line one\nnew two\nline three\nline four\n",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe("applyFold", () => {
|
|
28
|
+
test("with no fold state it returns the base rows unchanged", () => {
|
|
29
|
+
expect(applyFold(base, new Set(), new Set(), [contents])).toBe(base);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("a collapsed file keeps only its file band", () => {
|
|
33
|
+
// Act
|
|
34
|
+
const folded = applyFold(base, new Set(["a.ts"]), new Set(), [contents]);
|
|
35
|
+
|
|
36
|
+
// Assert
|
|
37
|
+
expect(folded).toHaveLength(1);
|
|
38
|
+
expect(folded[0]).toEqual(row("file", "a.ts"));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("an expanded file weaves the unchanged tail line the hunk never showed", () => {
|
|
42
|
+
// Act
|
|
43
|
+
const folded = applyFold(base, new Set(), new Set(["a.ts"]), [contents]);
|
|
44
|
+
|
|
45
|
+
// Assert - the hunk header is gone and "line four" (outside the hunk) now shows
|
|
46
|
+
expect(folded.some((r) => r.kind === "hunk")).toBe(false);
|
|
47
|
+
const four = folded.find((r) => r.text === "line four");
|
|
48
|
+
|
|
49
|
+
expect(four).toEqual(row("ctx", "line four", { oldLine: 4, newLine: 4 }));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("expand is a no-op without the file's full contents", () => {
|
|
53
|
+
// Act - no contents entry, so the file keeps its hunks
|
|
54
|
+
const folded = applyFold(base, new Set(), new Set(["a.ts"]), undefined);
|
|
55
|
+
|
|
56
|
+
// Assert
|
|
57
|
+
expect(folded).toEqual(base);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("weaveFullFileRows", () => {
|
|
62
|
+
test("keeps the change rows and fills every unchanged line as context in order", () => {
|
|
63
|
+
// Arrange - the file's code rows (no headers)
|
|
64
|
+
const codeRows = base.slice(2);
|
|
65
|
+
|
|
66
|
+
// Act
|
|
67
|
+
const woven = weaveFullFileRows("a.ts", codeRows, contents.newContents);
|
|
68
|
+
|
|
69
|
+
// Assert - full file: ctx1, del, add, ctx3, ctx4
|
|
70
|
+
expect(woven.map((r) => `${r.kind}:${r.text}`)).toEqual([
|
|
71
|
+
"ctx:line one",
|
|
72
|
+
"del:old two",
|
|
73
|
+
"add:new two",
|
|
74
|
+
"ctx:line three",
|
|
75
|
+
"ctx:line four",
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
});
|