@cueloop/client 0.1.0-alpha.8 → 0.1.0-alpha.80
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 +40 -32
- package/src/App.appearance.test.tsx +53 -0
- package/src/App.autoclose.test.tsx +77 -51
- package/src/App.diff.test.tsx +176 -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 +428 -0
- package/src/App.prototype-markdown.test.tsx +92 -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 +222 -0
- package/src/App.test.tsx +333 -131
- package/src/App.toast.test.tsx +98 -0
- package/src/App.tsx +1338 -608
- package/src/App.visual.test.tsx +57 -0
- package/src/App.walk.test.tsx +299 -0
- package/src/App.workbench.test.tsx +701 -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 +574 -0
- package/src/app-view-model.ts +162 -0
- package/src/attribution.test.ts +31 -0
- package/src/attribution.ts +42 -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.stories.tsx +89 -0
- package/src/components/AppShell.tsx +329 -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 +153 -0
- package/src/components/ConfirmDialog.stories.tsx +21 -0
- package/src/components/ConfirmDialog.tsx +72 -0
- package/src/components/DiffContentView.scroll.test.tsx +121 -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 +876 -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/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 +59 -0
- package/src/components/ProjectTreeView.tsx +111 -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 +899 -0
- package/src/components/ThreadView.slash.test.tsx +146 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +472 -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 +47 -0
- package/src/components/WelcomePlayground.tsx +142 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +2238 -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 +56 -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 +517 -33
- package/src/config.ts +467 -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 +13 -0
- package/src/integrations.test.ts +41 -0
- package/src/integrations.ts +32 -0
- package/src/intent-dispatch.test.ts +723 -0
- package/src/intent-dispatch.ts +692 -0
- package/src/key-bindings.test.ts +156 -0
- package/src/key-bindings.ts +459 -0
- package/src/keymap.test.ts +461 -113
- package/src/keymap.ts +321 -79
- package/src/kitty-image.test.ts +66 -0
- package/src/kitty-image.ts +138 -0
- package/src/launch-layout.test.ts +52 -0
- package/src/launch-layout.ts +42 -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 +64 -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 +78 -13
- 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 +214 -0
- package/src/theme.ts +84 -19
- package/src/thread-chords.test.ts +79 -0
- package/src/thread-chords.ts +163 -0
- package/src/thread-controller.curate.test.ts +450 -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 +1893 -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 +148 -0
- package/src/use-annotation-surface.tsx +915 -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-settings-dialog.tsx +320 -0
- package/src/use-terminal-virtualizer.ts +233 -0
- package/src/use-thread-body-editing.tsx +58 -0
- package/src/version.ts +5 -0
- package/src/view-diff.test.ts +137 -9
- package/src/view-diff.ts +269 -23
- package/src/view-plan.regression.test.ts +60 -0
- package/src/view-plan.test.ts +456 -0
- package/src/view-plan.ts +719 -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 +81 -0
- package/src/workbench-snapshot.ts +29 -0
- package/src/session-controller.ts +0 -345
- 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 -312
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** The share shortcut opens the two-column share dialog reachable from any view. */
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, mock, 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 { testRender } from "@opentui/react/test-utils";
|
|
9
|
+
import { DaemonServer } from "@cueloop/daemon";
|
|
10
|
+
import type { Thread } from "@cueloop/schema";
|
|
11
|
+
import { App } from "./App";
|
|
12
|
+
import type { ShareTransport } from "./thread-controller";
|
|
13
|
+
import {
|
|
14
|
+
isolateUserConfig,
|
|
15
|
+
press,
|
|
16
|
+
pressKey,
|
|
17
|
+
settle,
|
|
18
|
+
waitForText,
|
|
19
|
+
waitForTextGone,
|
|
20
|
+
} from "./test-support";
|
|
21
|
+
|
|
22
|
+
const shareTransport: ShareTransport = {
|
|
23
|
+
publish: mock(async () => ({ line: "ssh p_share01@cueloop.dev", copied: true })),
|
|
24
|
+
pull: mock(async () => {
|
|
25
|
+
throw new Error("Unexpected share pull");
|
|
26
|
+
}),
|
|
27
|
+
push: mock(async () => {}),
|
|
28
|
+
watch: () => () => {},
|
|
29
|
+
revoke: async () => {},
|
|
30
|
+
parseShareId: (line) => line.match(/^ssh (\S+)@/)?.[1],
|
|
31
|
+
formatShareLine: (id: string) => "ssh " + id + "@cueloop.dev",
|
|
32
|
+
collaboratorAnnotations: () => [],
|
|
33
|
+
mergeFromShare: () => ({ annotations: [] }),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const PLAN = `# Migration Plan\n\n## Context\n\nThe daemon persists sessions to disk atomically.\n`;
|
|
37
|
+
|
|
38
|
+
let home: string;
|
|
39
|
+
let restoreUserConfig: () => void;
|
|
40
|
+
let server: DaemonServer;
|
|
41
|
+
let session: Thread;
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
home = mkdtempSync(join(tmpdir(), "cueloop-share-choice-"));
|
|
45
|
+
restoreUserConfig = isolateUserConfig(home);
|
|
46
|
+
server = new DaemonServer({ home, idleExitMs: 0 });
|
|
47
|
+
server.start();
|
|
48
|
+
session = server.core.sessionCreate({
|
|
49
|
+
workspace: { repoRoot: "/repo", branch: "main" },
|
|
50
|
+
artifact: {
|
|
51
|
+
type: "plan",
|
|
52
|
+
content: PLAN,
|
|
53
|
+
meta: { title: "Migration Plan", planPath: "plan.md" },
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
restoreUserConfig();
|
|
59
|
+
server.stop();
|
|
60
|
+
rmSync(home, { recursive: true, force: true });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("share dialog", () => {
|
|
64
|
+
test("the shortcut opens the two-column share dialog", async () => {
|
|
65
|
+
// Arrange
|
|
66
|
+
const setup = await testRender(
|
|
67
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
68
|
+
{ width: 120, height: 32 },
|
|
69
|
+
);
|
|
70
|
+
await waitForText(setup, "cueloop");
|
|
71
|
+
|
|
72
|
+
// Act
|
|
73
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
74
|
+
|
|
75
|
+
// Assert - the nav categories and the empty-state new-link affordance
|
|
76
|
+
await waitForText(setup, "share externally");
|
|
77
|
+
await waitForText(setup, "export");
|
|
78
|
+
await waitForText(setup, "+ new link");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("new link opens the wizard, and require auth reveals the allowlist", async () => {
|
|
82
|
+
// Arrange
|
|
83
|
+
const setup = await testRender(
|
|
84
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
85
|
+
{ width: 120, height: 32 },
|
|
86
|
+
);
|
|
87
|
+
await waitForText(setup, "cueloop");
|
|
88
|
+
|
|
89
|
+
// Act: open the dialog, step into the body, activate "+ new link"
|
|
90
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
91
|
+
await waitForText(setup, "+ new link");
|
|
92
|
+
await press(setup, "enter");
|
|
93
|
+
await press(setup, "enter");
|
|
94
|
+
|
|
95
|
+
// Assert - the wizard's fields
|
|
96
|
+
await waitForText(setup, "link name");
|
|
97
|
+
await waitForText(setup, "require auth");
|
|
98
|
+
|
|
99
|
+
// Act: focus the auth row and switch it on (enter toggles the focused row)
|
|
100
|
+
await press(setup, "down");
|
|
101
|
+
await press(setup, "enter");
|
|
102
|
+
|
|
103
|
+
// Assert - the handles input appears with its grayed placeholder
|
|
104
|
+
await waitForText(setup, "handle");
|
|
105
|
+
|
|
106
|
+
// Act: backspace on the empty allowlist collapses require auth back off
|
|
107
|
+
await press(setup, "backspace");
|
|
108
|
+
|
|
109
|
+
// Assert - the handles section is gone
|
|
110
|
+
await waitForTextGone(setup, "handle");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("a private link with no handles cannot be created", async () => {
|
|
114
|
+
// Arrange
|
|
115
|
+
const setup = await testRender(
|
|
116
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
117
|
+
{ width: 120, height: 32 },
|
|
118
|
+
);
|
|
119
|
+
await waitForText(setup, "cueloop");
|
|
120
|
+
|
|
121
|
+
// Act: open the wizard, turn auth on but add no handles, then try to create
|
|
122
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
123
|
+
await waitForText(setup, "+ new link");
|
|
124
|
+
await press(setup, "enter"); // into the body
|
|
125
|
+
await press(setup, "enter"); // open the wizard
|
|
126
|
+
await waitForText(setup, "require auth");
|
|
127
|
+
await press(setup, "down"); // focus the auth row
|
|
128
|
+
await press(setup, "enter"); // toggle auth on (focus moves to the empty allowlist)
|
|
129
|
+
await press(setup, "down"); // move to the create action
|
|
130
|
+
await press(setup, "enter"); // create is blocked while the allowlist is empty
|
|
131
|
+
await settle(setup);
|
|
132
|
+
|
|
133
|
+
// Assert - the wizard is still open (nothing was created and the dialog did not fall back to the list)
|
|
134
|
+
expect(setup.captureCharFrame()).toContain("require auth");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("right enters the links body and left returns to the nav", async () => {
|
|
138
|
+
// Arrange
|
|
139
|
+
const setup = await testRender(
|
|
140
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
141
|
+
{ width: 120, height: 32 },
|
|
142
|
+
);
|
|
143
|
+
await waitForText(setup, "cueloop");
|
|
144
|
+
|
|
145
|
+
// Act + Assert: right arrow crosses into the body (its hint appears)
|
|
146
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
147
|
+
await waitForText(setup, "+ new link");
|
|
148
|
+
await press(setup, "right");
|
|
149
|
+
await waitForText(setup, "h back");
|
|
150
|
+
|
|
151
|
+
// Act + Assert: left arrow returns to the nav (its hint returns)
|
|
152
|
+
await press(setup, "left");
|
|
153
|
+
await waitForText(setup, "l/enter open");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("escape closes the dialog", async () => {
|
|
157
|
+
// Arrange
|
|
158
|
+
const setup = await testRender(
|
|
159
|
+
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
160
|
+
{ width: 120, height: 32 },
|
|
161
|
+
);
|
|
162
|
+
await waitForText(setup, "cueloop");
|
|
163
|
+
|
|
164
|
+
// Act
|
|
165
|
+
await pressKey(setup, "s", { ctrl: true });
|
|
166
|
+
await waitForText(setup, "share externally");
|
|
167
|
+
await press(setup, "escape");
|
|
168
|
+
|
|
169
|
+
// Assert
|
|
170
|
+
await waitForTextGone(setup, "share externally");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/** The send-message confirm: cmd+enter opens a centered overlay with a message selector, summary input, and word-buttons. Char-frame assertions over the real App and an in-process daemon, like App.test.tsx. */
|
|
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 { testRender } from "@opentui/react/test-utils";
|
|
9
|
+
import { DaemonServer } from "@cueloop/daemon";
|
|
10
|
+
import { makeAnchor, parseBlocks, type Thread } from "@cueloop/schema";
|
|
11
|
+
import { App } from "./App";
|
|
12
|
+
import {
|
|
13
|
+
isolateUserConfig,
|
|
14
|
+
press,
|
|
15
|
+
pressKey,
|
|
16
|
+
settle,
|
|
17
|
+
waitForText,
|
|
18
|
+
waitForTextGone,
|
|
19
|
+
} from "./test-support";
|
|
20
|
+
|
|
21
|
+
const PLAN = `# Migration Plan
|
|
22
|
+
|
|
23
|
+
## Context
|
|
24
|
+
|
|
25
|
+
The daemon persists sessions to disk atomically.
|
|
26
|
+
|
|
27
|
+
## Steps
|
|
28
|
+
|
|
29
|
+
- move the store
|
|
30
|
+
- add recovery
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
let home: string;
|
|
34
|
+
let restoreUserConfig: () => void;
|
|
35
|
+
let server: DaemonServer;
|
|
36
|
+
let session: Thread;
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
home = mkdtempSync(join(tmpdir(), "cueloop-confirm-"));
|
|
40
|
+
restoreUserConfig = isolateUserConfig(home);
|
|
41
|
+
server = new DaemonServer({ home, idleExitMs: 0 });
|
|
42
|
+
server.start();
|
|
43
|
+
session = server.core.sessionCreate({
|
|
44
|
+
workspace: { repoRoot: "/repo", branch: "main" },
|
|
45
|
+
artifact: {
|
|
46
|
+
type: "plan",
|
|
47
|
+
content: PLAN,
|
|
48
|
+
meta: { title: "Migration Plan", planPath: "plan.md" },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
restoreUserConfig();
|
|
54
|
+
server.stop();
|
|
55
|
+
rmSync(home, { recursive: true, force: true });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
async function renderApp(options: { readOnly?: boolean } = {}) {
|
|
59
|
+
const setup = await testRender(
|
|
60
|
+
<App home={home} sessionId={session.id} readOnly={options.readOnly ?? false} />,
|
|
61
|
+
{
|
|
62
|
+
width: 120,
|
|
63
|
+
height: 32,
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
await waitForText(setup, "cueloop");
|
|
68
|
+
|
|
69
|
+
return setup;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Seed annotations directly through the daemon core (all on one block). */
|
|
73
|
+
function seedAnnotations(count: number): void {
|
|
74
|
+
const blocks = parseBlocks(PLAN);
|
|
75
|
+
|
|
76
|
+
for (let index = 1; index <= count; index++) {
|
|
77
|
+
server.core.sessionAnnotate(session.id, {
|
|
78
|
+
id: `a_confirm_${index}`,
|
|
79
|
+
kind: "comment",
|
|
80
|
+
anchor: makeAnchor(blocks, 2, 0, 10),
|
|
81
|
+
body: `note ${String(index).padStart(2, "0")}`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe("send message confirm", () => {
|
|
87
|
+
test("cmd+enter opens the send-message confirm overlay", async () => {
|
|
88
|
+
// Arrange
|
|
89
|
+
const setup = await renderApp();
|
|
90
|
+
|
|
91
|
+
// Assert - the footer carries the send control before the overlay opens
|
|
92
|
+
expect(setup.captureCharFrame()).toContain("Send message (0)");
|
|
93
|
+
|
|
94
|
+
// Act
|
|
95
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
96
|
+
|
|
97
|
+
// Assert - the overlay: message selector and word-buttons
|
|
98
|
+
await waitForText(setup, "[Approve]"); // nothing pending: approve default
|
|
99
|
+
const frame = setup.captureCharFrame();
|
|
100
|
+
|
|
101
|
+
expect(frame).toContain("[Approve]");
|
|
102
|
+
expect(frame).toContain(" send ");
|
|
103
|
+
expect(frame).toContain(" cancel ");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("left/right cycles the message selector in the overlay", async () => {
|
|
107
|
+
// Arrange
|
|
108
|
+
const setup = await renderApp();
|
|
109
|
+
|
|
110
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
111
|
+
|
|
112
|
+
// Assert
|
|
113
|
+
await waitForText(setup, "[Approve]");
|
|
114
|
+
|
|
115
|
+
// Act
|
|
116
|
+
await press(setup, "right");
|
|
117
|
+
|
|
118
|
+
// Assert
|
|
119
|
+
await waitForText(setup, "[Changes]");
|
|
120
|
+
|
|
121
|
+
// Act
|
|
122
|
+
await press(setup, "right");
|
|
123
|
+
|
|
124
|
+
// Assert
|
|
125
|
+
await waitForText(setup, "[Approve]");
|
|
126
|
+
|
|
127
|
+
// Act
|
|
128
|
+
await press(setup, "left");
|
|
129
|
+
|
|
130
|
+
// Assert
|
|
131
|
+
await waitForText(setup, "[Changes]");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("esc closes the overlay", async () => {
|
|
135
|
+
// Arrange
|
|
136
|
+
const setup = await renderApp();
|
|
137
|
+
|
|
138
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
139
|
+
|
|
140
|
+
// Assert
|
|
141
|
+
await waitForText(setup, "[Approve]");
|
|
142
|
+
|
|
143
|
+
// Act
|
|
144
|
+
await press(setup, "escape");
|
|
145
|
+
|
|
146
|
+
// Assert - a bare ESC settles after the parser's escape-sequence window
|
|
147
|
+
const frame = await waitForTextGone(setup, "[Approve]");
|
|
148
|
+
|
|
149
|
+
expect(frame).not.toContain(" cancel ");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("enter in the overlay resolves the session through the controller", async () => {
|
|
153
|
+
// Arrange
|
|
154
|
+
seedAnnotations(1);
|
|
155
|
+
const setup = await renderApp();
|
|
156
|
+
|
|
157
|
+
// Act
|
|
158
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
159
|
+
|
|
160
|
+
// Assert - opens on the default message, then cycle to request changes
|
|
161
|
+
await waitForText(setup, "[Approve]");
|
|
162
|
+
await press(setup, "right");
|
|
163
|
+
await waitForText(setup, "[Changes]");
|
|
164
|
+
|
|
165
|
+
// Act
|
|
166
|
+
await setup.mockInput.typeText("Tighten the steps.");
|
|
167
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
168
|
+
|
|
169
|
+
// Assert - the completion flow after submit is unchanged
|
|
170
|
+
await waitForText(setup, "feedback sent");
|
|
171
|
+
const stored = server.core.sessionGet(session.id);
|
|
172
|
+
|
|
173
|
+
expect(stored.status).toBe("resolved");
|
|
174
|
+
expect(stored.message!.outcome).toBe("changes_requested");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("typing / in the summary opens the skills/actions palette", async () => {
|
|
178
|
+
// Arrange
|
|
179
|
+
seedAnnotations(1);
|
|
180
|
+
const setup = await renderApp();
|
|
181
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
182
|
+
await waitForText(setup, "[Approve]");
|
|
183
|
+
|
|
184
|
+
// Act
|
|
185
|
+
await setup.mockInput.typeText("/restate");
|
|
186
|
+
|
|
187
|
+
// Assert - the same slash palette the inline composer shows
|
|
188
|
+
await waitForText(setup, "restate-simplified");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("pasting an image into the summary drops in an [Image #n] placeholder", async () => {
|
|
192
|
+
// Arrange
|
|
193
|
+
const setup = await renderApp();
|
|
194
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
195
|
+
await waitForText(setup, "[Approve]");
|
|
196
|
+
|
|
197
|
+
// Act - a burst of control bytes stands in for the binary an image paste delivers
|
|
198
|
+
await setup.mockInput.typeText("look at this ");
|
|
199
|
+
await setup.mockInput.pasteBracketedText("");
|
|
200
|
+
|
|
201
|
+
// Assert - the raw bytes never reach the draft; a numbered placeholder does
|
|
202
|
+
await waitForText(setup, "[Image #1]");
|
|
203
|
+
expect(setup.captureCharFrame()).toContain("look at this [Image #1]");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("read-only observers cannot open the confirm overlay", async () => {
|
|
207
|
+
// Arrange
|
|
208
|
+
seedAnnotations(1);
|
|
209
|
+
const setup = await renderApp({ readOnly: true });
|
|
210
|
+
|
|
211
|
+
// Act
|
|
212
|
+
await pressKey(setup, "RETURN", { meta: true });
|
|
213
|
+
await settle(setup);
|
|
214
|
+
|
|
215
|
+
// Assert - no overlay, and the session stays pending
|
|
216
|
+
const frame = setup.captureCharFrame();
|
|
217
|
+
|
|
218
|
+
expect(frame).not.toContain("[Approve]");
|
|
219
|
+
expect(frame).not.toContain(" cancel ");
|
|
220
|
+
expect(server.core.sessionGet(session.id).status).toBe("pending");
|
|
221
|
+
});
|
|
222
|
+
});
|