@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,53 @@
|
|
|
1
|
+
/** The App renders in a light virtual terminal: passing appearance="light" threads the light branded theme through the provider without breaking the mount (the char frame is color-blind, so this covers propagation and wiring, not the pixel colors). */
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { DaemonServer } from "@cueloop/daemon";
|
|
9
|
+
import type { Thread } from "@cueloop/schema";
|
|
10
|
+
import { App } from "./App";
|
|
11
|
+
import { isolateUserConfig, settle, waitForText, renderReadyApp } from "../testing/test-support";
|
|
12
|
+
|
|
13
|
+
const PLAN = "# Sync Plan\n\n## Steps\n\n- flush the queue\n";
|
|
14
|
+
|
|
15
|
+
let home: string;
|
|
16
|
+
let server: DaemonServer;
|
|
17
|
+
let session: Thread;
|
|
18
|
+
let restoreUserConfig: () => void;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
home = mkdtempSync(join(tmpdir(), "cueloop-appearance-"));
|
|
22
|
+
restoreUserConfig = isolateUserConfig(home);
|
|
23
|
+
server = new DaemonServer({ home, idleExitMs: 0 });
|
|
24
|
+
server.start();
|
|
25
|
+
session = server.core.sessionCreate({
|
|
26
|
+
workspace: { repoRoot: "/repo", branch: "main" },
|
|
27
|
+
artifact: { type: "plan", content: PLAN, meta: { title: "Sync Plan", planPath: "plan.md" } },
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
restoreUserConfig();
|
|
32
|
+
server.stop();
|
|
33
|
+
rmSync(home, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("appearance", () => {
|
|
37
|
+
test("renders the review on a light terminal", async () => {
|
|
38
|
+
// Arrange + Act
|
|
39
|
+
const setup = await renderReadyApp(
|
|
40
|
+
<App home={home} sessionId={session.id} appearance="light" />,
|
|
41
|
+
{
|
|
42
|
+
width: 120,
|
|
43
|
+
height: 32,
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
await waitForText(setup, "cueloop");
|
|
49
|
+
await waitForText(setup, "Sync Plan");
|
|
50
|
+
await settle(setup);
|
|
51
|
+
expect(setup.captureCharFrame()).toContain("flush the queue");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Post-submit completion overlay: prompt, opt-in persistence, countdown, dismissal. */
|
|
1
|
+
/** Post-submit completion overlay: prompt, opt-in persistence, countdown, dismissal. Countdown seconds tick on an injected ManualClock, so tests advance time instead of waiting it out. */
|
|
2
2
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
4
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -6,113 +6,139 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import React from "react";
|
|
8
8
|
import { testRender } from "@opentui/react/test-utils";
|
|
9
|
+
import { ManualClock } from "@opentui/core/testing";
|
|
9
10
|
import { DaemonServer } from "@cueloop/daemon";
|
|
10
|
-
import type {
|
|
11
|
+
import type { Thread } from "@cueloop/schema";
|
|
11
12
|
import { App } from "./App";
|
|
13
|
+
import { isolateUserConfig, press, settle, waitForText, pressKey } from "../testing/test-support";
|
|
12
14
|
|
|
13
15
|
const PLAN = "# Plan\n\nShip the thing.\n";
|
|
14
16
|
|
|
15
17
|
let home: string;
|
|
16
18
|
let server: DaemonServer;
|
|
17
|
-
let session:
|
|
19
|
+
let session: Thread;
|
|
18
20
|
let configPath: string;
|
|
21
|
+
let restoreUserConfig: () => void;
|
|
22
|
+
let clock: ManualClock;
|
|
19
23
|
|
|
20
24
|
beforeEach(() => {
|
|
21
25
|
home = mkdtempSync(join(tmpdir(), "cueloop-close-"));
|
|
22
26
|
configPath = join(home, "config.toml");
|
|
23
|
-
|
|
27
|
+
restoreUserConfig = isolateUserConfig(home, "config.toml");
|
|
24
28
|
server = new DaemonServer({ home, idleExitMs: 0 });
|
|
25
29
|
server.start();
|
|
26
30
|
session = server.core.sessionCreate({
|
|
27
31
|
workspace: { repoRoot: "/repo", branch: "main" },
|
|
28
32
|
artifact: { type: "plan", content: PLAN, meta: { title: "Plan" } },
|
|
29
33
|
});
|
|
34
|
+
clock = new ManualClock();
|
|
30
35
|
});
|
|
31
36
|
afterEach(() => {
|
|
32
|
-
|
|
37
|
+
restoreUserConfig();
|
|
33
38
|
server.stop();
|
|
34
39
|
rmSync(home, { recursive: true, force: true });
|
|
35
40
|
});
|
|
36
41
|
|
|
37
42
|
async function renderApp(onExit?: (code: number) => void) {
|
|
38
|
-
const setup = await testRender(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const setup = await testRender(
|
|
44
|
+
<App home={home} sessionId={session.id} onExit={onExit} clock={clock} />,
|
|
45
|
+
{
|
|
46
|
+
width: 120,
|
|
47
|
+
height: 30,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await waitForText(setup, "cueloop");
|
|
52
|
+
|
|
46
53
|
return setup;
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
type Setup = Awaited<ReturnType<typeof renderApp>>;
|
|
50
|
-
async function press(setup: Setup, k: string): Promise<void> {
|
|
51
|
-
if (k === "enter") setup.mockInput.pressKey("RETURN");
|
|
52
|
-
else if (k === "escape") setup.mockInput.pressKey("ESCAPE");
|
|
53
|
-
else await setup.mockInput.typeText(k);
|
|
54
|
-
await Bun.sleep(15);
|
|
55
|
-
await setup.renderOnce();
|
|
56
|
-
}
|
|
57
57
|
|
|
58
58
|
async function submitApprove(setup: Setup): Promise<void> {
|
|
59
|
-
await
|
|
60
|
-
await
|
|
61
|
-
|
|
62
|
-
while (Date.now() < deadline && !setup.captureCharFrame().includes("review approved")) {
|
|
63
|
-
await Bun.sleep(25);
|
|
64
|
-
await setup.renderOnce();
|
|
65
|
-
}
|
|
59
|
+
await pressKey(setup, "RETURN", { meta: true }); // open submit (approve default: no pending items)
|
|
60
|
+
await pressKey(setup, "RETURN", { meta: true }); // confirm
|
|
61
|
+
await waitForText(setup, "review approved");
|
|
66
62
|
}
|
|
67
63
|
|
|
68
64
|
describe("completion overlay", () => {
|
|
69
|
-
test("submit with auto-close off
|
|
65
|
+
test("submit with auto-close off counts down from 5; ⏎ exits now", async () => {
|
|
66
|
+
// Arrange
|
|
70
67
|
let exited = -1;
|
|
71
68
|
const setup = await renderApp((code) => (exited = code));
|
|
69
|
+
|
|
70
|
+
// Act
|
|
72
71
|
await submitApprove(setup);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
|
|
73
|
+
// Assert
|
|
74
|
+
const frame = await waitForText(setup, "closing in 5s");
|
|
75
|
+
|
|
76
|
+
expect(frame).toContain("review approved");
|
|
77
|
+
expect(frame).toContain("close");
|
|
78
|
+
expect(frame).toContain("back to plan");
|
|
79
|
+
|
|
80
|
+
// Act
|
|
76
81
|
await press(setup, "enter");
|
|
82
|
+
|
|
83
|
+
// Assert
|
|
77
84
|
expect(exited).toBe(0);
|
|
78
85
|
});
|
|
79
86
|
|
|
80
|
-
test("a
|
|
87
|
+
test("a remembers the countdown as the persisted default", async () => {
|
|
88
|
+
// Arrange
|
|
81
89
|
const setup = await renderApp();
|
|
90
|
+
|
|
82
91
|
await submitApprove(setup);
|
|
92
|
+
await waitForText(setup, "closing in 5s");
|
|
93
|
+
|
|
94
|
+
// Act
|
|
83
95
|
await press(setup, "a");
|
|
84
|
-
|
|
96
|
+
|
|
97
|
+
// Assert
|
|
85
98
|
expect(existsSync(configPath)).toBe(true);
|
|
86
|
-
expect(readFileSync(configPath, "utf8")).toContain("auto_close =
|
|
99
|
+
expect(readFileSync(configPath, "utf8")).toContain("auto_close = 5");
|
|
100
|
+
|
|
101
|
+
// Act
|
|
102
|
+
// the countdown keeps running; each second is one manual-clock tick
|
|
103
|
+
clock.advance(1000);
|
|
104
|
+
|
|
105
|
+
// Assert
|
|
106
|
+
await waitForText(setup, "closing in 4s");
|
|
87
107
|
});
|
|
88
108
|
|
|
89
109
|
test("esc dismisses to the resolved read-only view", async () => {
|
|
110
|
+
// Arrange
|
|
90
111
|
const setup = await renderApp();
|
|
112
|
+
|
|
91
113
|
await submitApprove(setup);
|
|
92
|
-
await
|
|
114
|
+
await waitForText(setup, "closing in 5s");
|
|
115
|
+
|
|
116
|
+
// Act
|
|
93
117
|
// a bare ESC sits in the input parser's escape-sequence disambiguation
|
|
94
|
-
// window before it is delivered -
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
expect(frame).toContain("resolved: approve");
|
|
102
|
-
expect(frame).not.toContain("always close after submit");
|
|
118
|
+
// window before it is delivered - the frame wait absorbs it
|
|
119
|
+
await press(setup, "escape");
|
|
120
|
+
|
|
121
|
+
// Assert - the countdown is cancelled and we are back in the (resolved) thread view
|
|
122
|
+
const frame = await waitForText(setup, "Ship the thing");
|
|
123
|
+
|
|
124
|
+
expect(frame).not.toContain("closing in");
|
|
103
125
|
});
|
|
104
126
|
|
|
105
127
|
test("configured auto_close counts down and exits without interaction", async () => {
|
|
128
|
+
// Arrange
|
|
106
129
|
writeFileSync(configPath, "[ui]\nauto_close = 1\n");
|
|
107
130
|
let exited = -1;
|
|
108
131
|
const setup = await renderApp((code) => (exited = code));
|
|
132
|
+
|
|
109
133
|
await submitApprove(setup);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
134
|
+
await waitForText(setup, "closing in 1s");
|
|
135
|
+
expect(exited).toBe(-1);
|
|
136
|
+
|
|
137
|
+
// Act
|
|
138
|
+
clock.advance(1000);
|
|
139
|
+
await settle(setup);
|
|
140
|
+
|
|
141
|
+
// Assert
|
|
116
142
|
expect(exited).toBe(0);
|
|
117
|
-
}
|
|
143
|
+
});
|
|
118
144
|
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/** Keyboard scrolling in a changed-file tab uses caret navigation, even when the file was deleted. */
|
|
2
|
+
|
|
3
|
+
import { expect, test } from "bun:test";
|
|
4
|
+
import { mkdtempSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { ScrollBoxRenderable, type Renderable } from "@opentui/core";
|
|
9
|
+
import { DaemonServer } from "@cueloop/daemon";
|
|
10
|
+
import { App } from "./App";
|
|
11
|
+
import { annotationPaletteFor } from "../annotations/annotation-palette";
|
|
12
|
+
import { DARK } from "../appearance/theme";
|
|
13
|
+
import {
|
|
14
|
+
isolateUserConfig,
|
|
15
|
+
locateText,
|
|
16
|
+
press,
|
|
17
|
+
renderReadyApp,
|
|
18
|
+
settle,
|
|
19
|
+
waitForText,
|
|
20
|
+
} from "../testing/test-support";
|
|
21
|
+
|
|
22
|
+
function findScrollbox(node: Renderable): ScrollBoxRenderable | null {
|
|
23
|
+
if (node.id === "diff-scroll" && node instanceof ScrollBoxRenderable) return node;
|
|
24
|
+
|
|
25
|
+
for (const child of node.getChildren()) {
|
|
26
|
+
const found = findScrollbox(child);
|
|
27
|
+
|
|
28
|
+
if (found) return found;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function hex(color: { toInts(): [number, number, number, number] }): string {
|
|
35
|
+
const [red, green, blue] = color.toInts();
|
|
36
|
+
|
|
37
|
+
return "#" + [red, green, blue].map((part) => part.toString(16).padStart(2, "0")).join("");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test("deleted file caret scrolls one row and stays at the file boundaries", async () => {
|
|
41
|
+
const home = mkdtempSync(join(tmpdir(), "cueloop-scroll-home-"));
|
|
42
|
+
const repo = mkdtempSync(join(tmpdir(), "cueloop-scroll-repo-"));
|
|
43
|
+
const restoreConfig = isolateUserConfig(home);
|
|
44
|
+
const server = new DaemonServer({ home, idleExitMs: 0 });
|
|
45
|
+
let setup: Awaited<ReturnType<typeof renderReadyApp>> | undefined;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const file = join(repo, "long.md");
|
|
49
|
+
const lines = Array.from({ length: 180 }, (_, index) =>
|
|
50
|
+
index === 113
|
|
51
|
+
? "[nodejs/node#36005](https://github.com/nodejs/node/issues/36005)."
|
|
52
|
+
: `row ${String(index).padStart(3, "0")} ${"a paragraph with wrapped text and a link [source](https://example.test/page) ".repeat((index % 3) + 1)}`,
|
|
53
|
+
);
|
|
54
|
+
const git = (args: string[]) => Bun.spawnSync(["git", "-C", repo, ...args]);
|
|
55
|
+
|
|
56
|
+
writeFileSync(file, lines.join("\n") + "\n");
|
|
57
|
+
git(["init", "-q"]);
|
|
58
|
+
git(["config", "user.email", "fixture@example.com"]);
|
|
59
|
+
git(["config", "user.name", "Fixture"]);
|
|
60
|
+
git(["add", "-A"]);
|
|
61
|
+
git(["commit", "-q", "-m", "seed"]);
|
|
62
|
+
unlinkSync(file);
|
|
63
|
+
server.start();
|
|
64
|
+
setup = await renderReadyApp(<App home={home} sessionId={undefined} cwd={repo} />, {
|
|
65
|
+
width: 180,
|
|
66
|
+
height: 48,
|
|
67
|
+
});
|
|
68
|
+
await waitForText(setup, "long.md");
|
|
69
|
+
const treeFile = locateText(setup, "long.md");
|
|
70
|
+
|
|
71
|
+
await setup.mockMouse.click(treeFile.column, treeFile.row);
|
|
72
|
+
await waitForText(setup, "row 00");
|
|
73
|
+
const box = findScrollbox(setup.renderer.root);
|
|
74
|
+
|
|
75
|
+
if (!box) throw new Error("file diff scrollbox missing");
|
|
76
|
+
for (let offset = 0; offset < 1000; offset += 20) {
|
|
77
|
+
box.scrollTo({ x: 0, y: offset });
|
|
78
|
+
await settle(setup);
|
|
79
|
+
if (setup.captureCharFrame().includes("row 130")) break;
|
|
80
|
+
}
|
|
81
|
+
const target = locateText(setup, "row 130");
|
|
82
|
+
|
|
83
|
+
await setup.mockMouse.click(target.column, target.row);
|
|
84
|
+
await press(setup, "escape");
|
|
85
|
+
|
|
86
|
+
const steps: number[] = [];
|
|
87
|
+
const caretCell = annotationPaletteFor(DARK).caretCell;
|
|
88
|
+
const frameTrace: Array<{ markers: number[]; caret: number }> = [];
|
|
89
|
+
const captureFrame = () => {
|
|
90
|
+
const lines = setup!.captureCharFrame().split("\n");
|
|
91
|
+
const markers = lines.flatMap((line, row) => (line.includes("▎") ? [row] : []));
|
|
92
|
+
const caret = setup!
|
|
93
|
+
.captureSpans()
|
|
94
|
+
.lines.findIndex((line) => line.spans.some((span) => hex(span.bg) === caretCell));
|
|
95
|
+
|
|
96
|
+
frameTrace.push({ markers, caret });
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
setup.renderer.on("frame", captureFrame);
|
|
100
|
+
|
|
101
|
+
for (let index = 0; index < 12; index++) {
|
|
102
|
+
const before = box.scrollTop;
|
|
103
|
+
|
|
104
|
+
await press(setup, "down");
|
|
105
|
+
steps.push(box.scrollTop - before);
|
|
106
|
+
const caretRow = setup
|
|
107
|
+
.captureSpans()
|
|
108
|
+
.lines.findIndex((line) => line.spans.some((span) => hex(span.bg) === caretCell));
|
|
109
|
+
const markerRows = setup
|
|
110
|
+
.captureCharFrame()
|
|
111
|
+
.split("\n")
|
|
112
|
+
.flatMap((line, row) => (line.includes("▎") ? [row] : []));
|
|
113
|
+
|
|
114
|
+
expect(caretRow).toBeGreaterThanOrEqual(0);
|
|
115
|
+
expect(markerRows).toEqual([caretRow]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
expect(Math.max(...steps)).toBeLessThanOrEqual(1);
|
|
119
|
+
expect(Math.min(...steps)).toBeGreaterThanOrEqual(0);
|
|
120
|
+
let anchored = false;
|
|
121
|
+
|
|
122
|
+
for (let index = 0; index < 80; index++) {
|
|
123
|
+
await press(setup, "up");
|
|
124
|
+
const marker = setup
|
|
125
|
+
.captureCharFrame()
|
|
126
|
+
.split("\n")
|
|
127
|
+
.findIndex((line) => line.includes("▎"));
|
|
128
|
+
|
|
129
|
+
if (marker === box.viewport.screenY) anchored = true;
|
|
130
|
+
if (anchored && box.scrollTop > 0) expect(marker).toBe(box.viewport.screenY);
|
|
131
|
+
}
|
|
132
|
+
expect(anchored).toBe(true);
|
|
133
|
+
box.scrollTo({ x: 0, y: 100000 });
|
|
134
|
+
await settle(setup);
|
|
135
|
+
const last = locateText(setup, "row 179");
|
|
136
|
+
|
|
137
|
+
await setup.mockMouse.click(last.column, last.row);
|
|
138
|
+
await press(setup, "escape");
|
|
139
|
+
let lastMarker = -1;
|
|
140
|
+
|
|
141
|
+
for (let index = 0; index < 12; index++) {
|
|
142
|
+
const traceStart = frameTrace.length;
|
|
143
|
+
const before = box.scrollTop;
|
|
144
|
+
|
|
145
|
+
await press(setup, "down");
|
|
146
|
+
const states = frameTrace.slice(traceStart);
|
|
147
|
+
|
|
148
|
+
expect(box.scrollTop).toBe(before);
|
|
149
|
+
expect(states.length).toBeGreaterThan(0);
|
|
150
|
+
for (const state of states) {
|
|
151
|
+
expect(state.markers).toEqual([state.caret]);
|
|
152
|
+
expect(state.caret).toBeGreaterThanOrEqual(lastMarker);
|
|
153
|
+
lastMarker = state.caret;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
box.scrollTo({ x: 0, y: 0 });
|
|
157
|
+
await settle(setup);
|
|
158
|
+
const first = locateText(setup, "row 000");
|
|
159
|
+
|
|
160
|
+
await setup.mockMouse.click(first.column, first.row);
|
|
161
|
+
await press(setup, "escape");
|
|
162
|
+
let firstMarker = Number.POSITIVE_INFINITY;
|
|
163
|
+
|
|
164
|
+
for (let index = 0; index < 12; index++) {
|
|
165
|
+
const traceStart = frameTrace.length;
|
|
166
|
+
const before = box.scrollTop;
|
|
167
|
+
|
|
168
|
+
await press(setup, "up");
|
|
169
|
+
const states = frameTrace.slice(traceStart);
|
|
170
|
+
|
|
171
|
+
expect(box.scrollTop).toBe(before);
|
|
172
|
+
expect(states.length).toBeGreaterThan(0);
|
|
173
|
+
for (const state of states) {
|
|
174
|
+
expect(state.markers).toEqual([state.caret]);
|
|
175
|
+
expect(state.caret).toBeLessThanOrEqual(firstMarker);
|
|
176
|
+
firstMarker = state.caret;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
setup.renderer.off("frame", captureFrame);
|
|
180
|
+
} finally {
|
|
181
|
+
setup?.renderer.destroy();
|
|
182
|
+
server.stop();
|
|
183
|
+
restoreConfig();
|
|
184
|
+
rmSync(home, { recursive: true, force: true });
|
|
185
|
+
rmSync(repo, { recursive: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
}, 30000);
|