@cueloop/client 0.1.0-alpha.79 → 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/package.json +5 -5
- package/src/App.diff.test.tsx +2 -2
- package/src/App.export.test.tsx +3 -3
- package/src/App.plan-review.test.tsx +1 -1
- package/src/App.readonly.test.tsx +2 -2
- package/src/App.submit-confirm.test.tsx +7 -7
- package/src/App.test.tsx +11 -11
- package/src/App.tsx +16 -11
- package/src/App.walk.test.tsx +5 -5
- package/src/app-key-state.ts +1 -1
- package/src/app-screens.tsx +5 -5
- package/src/app-view-model.ts +5 -5
- package/src/components/AnnotatableFileView.stories.tsx +1 -1
- package/src/components/AnnotatableFileView.test.tsx +1 -1
- package/src/components/BareWorkbenchFileView.tsx +1 -1
- package/src/components/Breadcrumb.stories.tsx +1 -1
- package/src/components/ChangesColumn.stories.tsx +6 -1
- package/src/components/CompletionOverlay.stories.tsx +2 -2
- package/src/components/CompletionOverlay.tsx +4 -4
- package/src/components/ConfirmCard.stories.tsx +4 -4
- package/src/components/ConfirmCard.tsx +31 -32
- package/src/components/DiffContentView.tsx +1 -1
- package/src/components/KeybindsDialog.stories.tsx +1 -1
- package/src/components/ThreadFooter.test.tsx +12 -1
- package/src/components/ThreadFooter.tsx +7 -2
- package/src/components/ThreadTree.stories.tsx +1 -1
- package/src/components/ThreadTree.tsx +1 -1
- package/src/components/ThreadView.tsx +1 -1
- package/src/components/WelcomePlayground.tsx +1 -1
- package/src/components/__snapshots__/stories.test.tsx.snap +8 -8
- package/src/components/story-fixtures.ts +2 -2
- package/src/components/{session-tree.test.ts → thread-tree.test.ts} +2 -2
- package/src/config.test.ts +10 -10
- package/src/config.ts +6 -9
- package/src/integrations.test.ts +8 -8
- package/src/integrations.ts +4 -4
- package/src/intent-dispatch.test.ts +10 -10
- package/src/intent-dispatch.ts +15 -14
- package/src/key-bindings.test.ts +4 -4
- package/src/key-bindings.ts +10 -10
- package/src/keymap.test.ts +5 -5
- package/src/keymap.ts +7 -7
- package/src/thread-controller.curate.test.ts +15 -5
- package/src/thread-controller.derivation.test.ts +1 -1
- package/src/thread-controller.live-diff.test.ts +10 -10
- package/src/thread-controller.optimistic.test.ts +1 -1
- package/src/thread-controller.share.test.ts +8 -8
- package/src/thread-controller.switch-cache.test.ts +4 -4
- package/src/thread-controller.tree.test.ts +4 -4
- package/src/thread-controller.ts +12 -12
- package/src/tree-view.test.ts +1 -1
- package/src/tree-view.ts +3 -3
- package/src/use-annotation-surface.tsx +1 -1
- package/src/view-diff.test.ts +1 -1
- package/src/workbench-snapshot.test.ts +1 -1
- /package/src/components/{session-tree.ts → thread-tree.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cueloop/client",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.80",
|
|
4
4
|
"description": "The cueloop review UI: projection renderer, keyboard grammar, review rail",
|
|
5
5
|
"homepage": "https://github.com/mmurakaru/cueloop#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cueloop/daemon": "0.1.0-alpha.
|
|
31
|
-
"@cueloop/extension-api": "0.1.0-alpha.
|
|
32
|
-
"@cueloop/integration-obsidian": "0.1.0-alpha.
|
|
33
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
30
|
+
"@cueloop/daemon": "0.1.0-alpha.80",
|
|
31
|
+
"@cueloop/extension-api": "0.1.0-alpha.80",
|
|
32
|
+
"@cueloop/integration-obsidian": "0.1.0-alpha.80",
|
|
33
|
+
"@cueloop/schema": "0.1.0-alpha.80",
|
|
34
34
|
"@opentui/core": "^0.5.11",
|
|
35
35
|
"@opentui/keymap": "0.5.11",
|
|
36
36
|
"@opentui/react": "^0.5.11",
|
package/src/App.diff.test.tsx
CHANGED
|
@@ -105,8 +105,8 @@ describe("diff review", () => {
|
|
|
105
105
|
await waitForText(setup, "feedback sent");
|
|
106
106
|
const resolved = server.core.sessionGet(session.id);
|
|
107
107
|
|
|
108
|
-
expect(resolved.
|
|
109
|
-
expect(resolved.
|
|
108
|
+
expect(resolved.message!.body).toContain("new Map()");
|
|
109
|
+
expect(resolved.message!.body).toContain("Map needs an eviction story.");
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
test("pasting an image into a diff comment drops in an [Image #n] placeholder", async () => {
|
package/src/App.export.test.tsx
CHANGED
|
@@ -43,7 +43,7 @@ beforeEach(() => {
|
|
|
43
43
|
|
|
44
44
|
writeFileSync(
|
|
45
45
|
configPath,
|
|
46
|
-
`[integrations.obsidian]\nvault = ${JSON.stringify(vault)}\nexportOn = "
|
|
46
|
+
`[integrations.obsidian]\nvault = ${JSON.stringify(vault)}\nexportOn = "message"\n`,
|
|
47
47
|
);
|
|
48
48
|
restoreUserConfig = isolateUserConfig(home, "config.toml");
|
|
49
49
|
server = new DaemonServer({ home, idleExitMs: 0 });
|
|
@@ -74,7 +74,7 @@ describe("obsidian export on resolve", () => {
|
|
|
74
74
|
await waitForText(setup, "cueloop");
|
|
75
75
|
|
|
76
76
|
// Act
|
|
77
|
-
await pressKey(setup, "RETURN", { meta: true }); // open the submit overlay, keep the default
|
|
77
|
+
await pressKey(setup, "RETURN", { meta: true }); // open the submit overlay, keep the default message (approve)
|
|
78
78
|
|
|
79
79
|
// Assert
|
|
80
80
|
await waitForText(setup, "[Approve]");
|
|
@@ -98,7 +98,7 @@ describe("obsidian export on resolve", () => {
|
|
|
98
98
|
|
|
99
99
|
expect(written).toContain("source: cueloop");
|
|
100
100
|
expect(written).toContain(`session: ${session.id}`);
|
|
101
|
-
expect(written).toContain("
|
|
101
|
+
expect(written).toContain("message: approved");
|
|
102
102
|
expect(written).toContain("Move the store atomically.");
|
|
103
103
|
expect(setup.captureCharFrame()).toContain("exported to");
|
|
104
104
|
});
|
|
@@ -125,7 +125,7 @@ describe("share button", () => {
|
|
|
125
125
|
|
|
126
126
|
test("a resolved plan hides Edit and Share (no re-sharing a finished review)", async () => {
|
|
127
127
|
// Arrange - resolve the session before opening it
|
|
128
|
-
server.core.
|
|
128
|
+
server.core.sessionSendMessage(session.id, "approved", "");
|
|
129
129
|
|
|
130
130
|
// Act
|
|
131
131
|
const setup = await renderApp();
|
|
@@ -137,14 +137,14 @@ describe("observer primitives are blocked", () => {
|
|
|
137
137
|
|
|
138
138
|
describe("a resolved review is read-only for its owner too", () => {
|
|
139
139
|
test("typing, deleting a card, and editing answer review submitted - read-only", async () => {
|
|
140
|
-
// Arrange: a comment exists and the
|
|
140
|
+
// Arrange: a comment exists and the message is in
|
|
141
141
|
server.core.sessionAnnotate(session.id, {
|
|
142
142
|
id: "a_done",
|
|
143
143
|
kind: "comment",
|
|
144
144
|
anchor: makeAnchor(parseBlocks(PLAN), 2, 0, 10),
|
|
145
145
|
body: "settled",
|
|
146
146
|
});
|
|
147
|
-
server.core.
|
|
147
|
+
server.core.sessionSendMessage(session.id, "approved", "");
|
|
148
148
|
const setup = await renderReadyApp(<App home={home} sessionId={session.id} />, {
|
|
149
149
|
width: 120,
|
|
150
150
|
height: 32,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The send-message confirm: cmd+enter opens a centered overlay with a
|
|
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
2
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
4
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
@@ -89,12 +89,12 @@ describe("send message confirm", () => {
|
|
|
89
89
|
const setup = await renderApp();
|
|
90
90
|
|
|
91
91
|
// Assert - the footer carries the send control before the overlay opens
|
|
92
|
-
expect(setup.captureCharFrame()).toContain("
|
|
92
|
+
expect(setup.captureCharFrame()).toContain("Send message (0)");
|
|
93
93
|
|
|
94
94
|
// Act
|
|
95
95
|
await pressKey(setup, "RETURN", { meta: true });
|
|
96
96
|
|
|
97
|
-
// Assert - the overlay:
|
|
97
|
+
// Assert - the overlay: message selector and word-buttons
|
|
98
98
|
await waitForText(setup, "[Approve]"); // nothing pending: approve default
|
|
99
99
|
const frame = setup.captureCharFrame();
|
|
100
100
|
|
|
@@ -103,7 +103,7 @@ describe("send message confirm", () => {
|
|
|
103
103
|
expect(frame).toContain(" cancel ");
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
test("left/right cycles the
|
|
106
|
+
test("left/right cycles the message selector in the overlay", async () => {
|
|
107
107
|
// Arrange
|
|
108
108
|
const setup = await renderApp();
|
|
109
109
|
|
|
@@ -122,7 +122,7 @@ describe("send message confirm", () => {
|
|
|
122
122
|
await press(setup, "right");
|
|
123
123
|
|
|
124
124
|
// Assert
|
|
125
|
-
await waitForText(setup, "[
|
|
125
|
+
await waitForText(setup, "[Approve]");
|
|
126
126
|
|
|
127
127
|
// Act
|
|
128
128
|
await press(setup, "left");
|
|
@@ -157,7 +157,7 @@ describe("send message confirm", () => {
|
|
|
157
157
|
// Act
|
|
158
158
|
await pressKey(setup, "RETURN", { meta: true });
|
|
159
159
|
|
|
160
|
-
// Assert - opens on the default
|
|
160
|
+
// Assert - opens on the default message, then cycle to request changes
|
|
161
161
|
await waitForText(setup, "[Approve]");
|
|
162
162
|
await press(setup, "right");
|
|
163
163
|
await waitForText(setup, "[Changes]");
|
|
@@ -171,7 +171,7 @@ describe("send message confirm", () => {
|
|
|
171
171
|
const stored = server.core.sessionGet(session.id);
|
|
172
172
|
|
|
173
173
|
expect(stored.status).toBe("resolved");
|
|
174
|
-
expect(stored.
|
|
174
|
+
expect(stored.message!.outcome).toBe("changes_requested");
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
test("typing / in the summary opens the skills/actions palette", async () => {
|
package/src/App.test.tsx
CHANGED
|
@@ -79,7 +79,7 @@ describe("plan rendering", () => {
|
|
|
79
79
|
expect(frame).toContain("Context");
|
|
80
80
|
expect(frame).toContain("persists sessions to disk atomically");
|
|
81
81
|
expect(frame).toContain("· move the store");
|
|
82
|
-
expect(frame).toContain("
|
|
82
|
+
expect(frame).toContain("Send message (");
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
test("a direct open still populates the Threads sidebar with other pending reviews", async () => {
|
|
@@ -191,7 +191,7 @@ describe("thread view grammar", () => {
|
|
|
191
191
|
});
|
|
192
192
|
|
|
193
193
|
describe("submit", () => {
|
|
194
|
-
test("cmd+⏎ opens the rail confirm card;
|
|
194
|
+
test("cmd+⏎ opens the rail confirm card; message + summary resolve the session", async () => {
|
|
195
195
|
// Arrange
|
|
196
196
|
const setup = await renderApp();
|
|
197
197
|
|
|
@@ -203,7 +203,7 @@ describe("submit", () => {
|
|
|
203
203
|
// Act: with no composer open the same chord opens submit
|
|
204
204
|
await pressKey(setup, "RETURN", { meta: true });
|
|
205
205
|
|
|
206
|
-
// Assert - the card opens on the default
|
|
206
|
+
// Assert - the card opens on the default message, approve
|
|
207
207
|
await waitForText(setup, "[Approve]");
|
|
208
208
|
|
|
209
209
|
// Act - cycle to request changes, then send with a summary
|
|
@@ -217,13 +217,13 @@ describe("submit", () => {
|
|
|
217
217
|
const stored = server.core.sessionGet(session.id);
|
|
218
218
|
|
|
219
219
|
expect(stored.status).toBe("resolved");
|
|
220
|
-
expect(stored.
|
|
221
|
-
expect(stored.
|
|
220
|
+
expect(stored.message!.outcome).toBe("changes_requested");
|
|
221
|
+
expect(stored.message!.body).toContain("Needs a phase list.");
|
|
222
222
|
// submit hands the reviewer back to the agent via the completion overlay
|
|
223
223
|
expect(setup.captureCharFrame()).toContain("feedback sent");
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
-
test("approve via ←/→
|
|
226
|
+
test("approve via ←/→ message cycling", async () => {
|
|
227
227
|
// Arrange
|
|
228
228
|
const setup = await renderApp();
|
|
229
229
|
|
|
@@ -237,8 +237,8 @@ describe("submit", () => {
|
|
|
237
237
|
await pressKey(setup, "RETURN", { meta: true });
|
|
238
238
|
|
|
239
239
|
// Assert
|
|
240
|
-
await waitForState(setup, () => server.core.sessionGet(session.id).
|
|
241
|
-
expect(server.core.sessionGet(session.id).
|
|
240
|
+
await waitForState(setup, () => server.core.sessionGet(session.id).message !== undefined);
|
|
241
|
+
expect(server.core.sessionGet(session.id).message!.outcome).toBe("approved");
|
|
242
242
|
});
|
|
243
243
|
});
|
|
244
244
|
|
|
@@ -261,7 +261,7 @@ describe("no-thread shell", () => {
|
|
|
261
261
|
await press(setup, "enter");
|
|
262
262
|
|
|
263
263
|
// Assert
|
|
264
|
-
await waitForText(setup, "
|
|
264
|
+
await waitForText(setup, "Send message (");
|
|
265
265
|
});
|
|
266
266
|
|
|
267
267
|
test("picking a thread from the no-thread shell keeps the Threads sidebar open", async () => {
|
|
@@ -278,7 +278,7 @@ describe("no-thread shell", () => {
|
|
|
278
278
|
|
|
279
279
|
// Act - open the thread under the cursor
|
|
280
280
|
await press(setup, "enter");
|
|
281
|
-
await waitForText(setup, "
|
|
281
|
+
await waitForText(setup, "Send message (");
|
|
282
282
|
|
|
283
283
|
// Assert - the sidebar stayed open across the swap: both titles are on screen,
|
|
284
284
|
// and the non-opened one can only come from the still-open sidebar
|
|
@@ -344,7 +344,7 @@ describe("no-thread shell", () => {
|
|
|
344
344
|
await press(setup, "enter");
|
|
345
345
|
|
|
346
346
|
// Assert
|
|
347
|
-
await waitForText(setup, "
|
|
347
|
+
await waitForText(setup, "Send message (");
|
|
348
348
|
});
|
|
349
349
|
});
|
|
350
350
|
|
package/src/App.tsx
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
} from "./theme-presets";
|
|
36
36
|
import type { Theme } from "./theme";
|
|
37
37
|
import { createReviewController, type ShareTransport } from "./thread-controller";
|
|
38
|
-
import type {
|
|
38
|
+
import type { ThreadClient } from "@cueloop/daemon/client";
|
|
39
39
|
import { createIntentDispatch, type Mode, type RailTab } from "./intent-dispatch";
|
|
40
40
|
import { reduceKey, type KeyState } from "./keymap";
|
|
41
41
|
import { KeyBindings, type CheatsheetSection } from "./key-bindings";
|
|
@@ -45,7 +45,7 @@ import { Button } from "./components/primitives/Button";
|
|
|
45
45
|
import { ShareDialog } from "./components/ShareDialog";
|
|
46
46
|
import { shareDialogStore } from "./components/share-dialog-store";
|
|
47
47
|
import { Toolbar } from "./components/primitives/Toolbar";
|
|
48
|
-
import { groupInbox, projectName, threadTitle } from "./components/
|
|
48
|
+
import { groupInbox, projectName, threadTitle } from "./components/thread-tree";
|
|
49
49
|
import { ThreadTree } from "./components/ThreadTree";
|
|
50
50
|
import { ChangesFileTree } from "./components/ChangesColumn";
|
|
51
51
|
import { MenuControlProvider, useMenuControlState } from "./components/menu-control";
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
} from "./thread-chords";
|
|
71
71
|
import { type DiffFoldControls } from "./components/DiffContentView";
|
|
72
72
|
import { commentCountsByFile } from "./view-diff";
|
|
73
|
-
import { annotationTarget, threadShareLinks } from "@cueloop/schema";
|
|
73
|
+
import { annotationTarget, isAddressed, isAgentNote, threadShareLinks } from "@cueloop/schema";
|
|
74
74
|
import type {
|
|
75
75
|
Annotation,
|
|
76
76
|
Artifact,
|
|
@@ -78,7 +78,7 @@ import type {
|
|
|
78
78
|
Identity,
|
|
79
79
|
ShareLink,
|
|
80
80
|
Thread,
|
|
81
|
-
|
|
81
|
+
MessageOutcome,
|
|
82
82
|
} from "@cueloop/schema";
|
|
83
83
|
import { PrototypePixels } from "./prototype-pixels";
|
|
84
84
|
import type { PrototypeElement } from "./prototype-browser";
|
|
@@ -121,12 +121,12 @@ export interface AppProps {
|
|
|
121
121
|
/** Timer source for the auto-close countdown; tests inject a ManualClock. */
|
|
122
122
|
clock?: Clock;
|
|
123
123
|
/** Session source; the sharing gateway injects a blob-backed client. */
|
|
124
|
-
openClient?: () => Promise<
|
|
124
|
+
openClient?: () => Promise<ThreadClient>;
|
|
125
125
|
shareTransport?: ShareTransport;
|
|
126
126
|
/**
|
|
127
127
|
* Who is at the keyboard. `owner` is the local planner (default). `observer`
|
|
128
128
|
* is a passive `cueloop serve` watcher (read-only). `collaborator` is a share
|
|
129
|
-
* viewer: annotates, but cannot edit the plan or submit an agent
|
|
129
|
+
* viewer: annotates, but cannot edit the plan or submit an agent message.
|
|
130
130
|
*/
|
|
131
131
|
role?: "owner" | "observer" | "collaborator";
|
|
132
132
|
/**
|
|
@@ -581,7 +581,7 @@ export function App({
|
|
|
581
581
|
const [autoClose, setAutoClose] = useState<AutoClose>("off");
|
|
582
582
|
// unified or side-by-side diff; split only lays out when the Changes pane is zoomed
|
|
583
583
|
const [diffView, setDiffView] = useState<DiffViewMode>("split");
|
|
584
|
-
const [
|
|
584
|
+
const [defaultMessage, setDefaultMessage] = useState<MessageOutcome>("approved");
|
|
585
585
|
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | undefined>(undefined);
|
|
586
586
|
const [selectedCurationId, setSelectedCurationId] = useState<string | undefined>(undefined);
|
|
587
587
|
const [railTab, setRailTab] = useState<RailTab>("review");
|
|
@@ -625,7 +625,7 @@ export function App({
|
|
|
625
625
|
setSkills(loadSkills(config.skillsPath));
|
|
626
626
|
setAutoClose(config.ui.autoClose);
|
|
627
627
|
setDiffView(config.ui.diffView);
|
|
628
|
-
|
|
628
|
+
setDefaultMessage(config.ui.defaultMessage);
|
|
629
629
|
setPinnedIds(new Set(config.ui.pins));
|
|
630
630
|
controller.applyConfig(config);
|
|
631
631
|
}, [session?.workspace.repoRoot, controller, keyBindings, appearance]);
|
|
@@ -827,7 +827,7 @@ export function App({
|
|
|
827
827
|
inboxCursor,
|
|
828
828
|
mode,
|
|
829
829
|
session,
|
|
830
|
-
|
|
830
|
+
defaultMessage,
|
|
831
831
|
focusedAnnotationId,
|
|
832
832
|
selectedCurationId,
|
|
833
833
|
railTab,
|
|
@@ -1057,12 +1057,12 @@ export function App({
|
|
|
1057
1057
|
authorNames,
|
|
1058
1058
|
);
|
|
1059
1059
|
|
|
1060
|
-
if (isCompletionOverlayPhase(completion) && activeSession.
|
|
1060
|
+
if (isCompletionOverlayPhase(completion) && activeSession.message)
|
|
1061
1061
|
return (
|
|
1062
1062
|
<CompletionScreen
|
|
1063
1063
|
theme={theme}
|
|
1064
1064
|
session={activeSession}
|
|
1065
|
-
|
|
1065
|
+
message={activeSession.message.outcome}
|
|
1066
1066
|
completion={completion}
|
|
1067
1067
|
status={status}
|
|
1068
1068
|
onClose={() => dispatch({ type: "finishReview" })}
|
|
@@ -1123,6 +1123,11 @@ export function App({
|
|
|
1123
1123
|
branch={activeSession.workspace.branch}
|
|
1124
1124
|
onSubmit={onSubmitRequest}
|
|
1125
1125
|
canSubmit={canSubmitReview(isOwner, resolved, observer)}
|
|
1126
|
+
pendingAnnotations={
|
|
1127
|
+
activeSession.annotations.filter(
|
|
1128
|
+
(annotation) => !isAddressed(annotation) && !isAgentNote(annotation),
|
|
1129
|
+
).length
|
|
1130
|
+
}
|
|
1126
1131
|
theme={theme}
|
|
1127
1132
|
/>
|
|
1128
1133
|
);
|
package/src/App.walk.test.tsx
CHANGED
|
@@ -159,7 +159,7 @@ describe("the guided walk", () => {
|
|
|
159
159
|
await press(setup, "enter");
|
|
160
160
|
|
|
161
161
|
// Assert
|
|
162
|
-
await waitForText(setup, "
|
|
162
|
+
await waitForText(setup, "Send message (");
|
|
163
163
|
expect(setup.captureCharFrame()).toContain("3/3 files viewed");
|
|
164
164
|
|
|
165
165
|
// Act
|
|
@@ -248,7 +248,7 @@ describe("the guided walk", () => {
|
|
|
248
248
|
|
|
249
249
|
// Act
|
|
250
250
|
// a note is agent context, not reviewer feedback: it neither counts nor
|
|
251
|
-
// flips the default
|
|
251
|
+
// flips the default message, and never comes back in the feedback doc
|
|
252
252
|
await press(setup, "escape");
|
|
253
253
|
await waitForTextGone(setup, "agent note");
|
|
254
254
|
await pressKey(setup, "RETURN", { meta: true });
|
|
@@ -260,8 +260,8 @@ describe("the guided walk", () => {
|
|
|
260
260
|
await waitForState(setup, () => server.core.sessionGet(session.id).status === "resolved");
|
|
261
261
|
const resolved = server.core.sessionGet(session.id);
|
|
262
262
|
|
|
263
|
-
expect(resolved.
|
|
264
|
-
expect(resolved.
|
|
263
|
+
expect(resolved.message!.outcome).toBe("approved");
|
|
264
|
+
expect(resolved.message!.body).not.toContain("Swaps the stale line");
|
|
265
265
|
});
|
|
266
266
|
|
|
267
267
|
test("the sheet dims behind the wizard; the preview keeps the diff colors", async () => {
|
|
@@ -286,7 +286,7 @@ describe("the guided walk", () => {
|
|
|
286
286
|
|
|
287
287
|
test("walking a resolved review answers read-only", async () => {
|
|
288
288
|
// Arrange
|
|
289
|
-
server.core.
|
|
289
|
+
server.core.sessionSendMessage(session.id, "approved", "");
|
|
290
290
|
const setup = await renderApp();
|
|
291
291
|
|
|
292
292
|
// Act - renderApp already waited for the diff to load; the session is resolved
|
package/src/app-key-state.ts
CHANGED
package/src/app-screens.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ScrollArea } from "./components/ScrollArea";
|
|
2
2
|
import React, { useEffect, useMemo, useState, type Dispatch, type SetStateAction } from "react";
|
|
3
|
-
import type { DiffFileContents, Thread,
|
|
3
|
+
import type { DiffFileContents, Thread, MessageOutcome } from "@cueloop/schema";
|
|
4
4
|
import { returnPaneFor } from "@cueloop/schema";
|
|
5
5
|
import type { Theme } from "./theme";
|
|
6
6
|
import type { QuickAction } from "./config";
|
|
@@ -16,7 +16,7 @@ import type { SettingsCategory, SettingsValues } from "./components/SettingsDial
|
|
|
16
16
|
import type { SettingsNav } from "./use-settings-dialog";
|
|
17
17
|
import { CLIENT_VERSION } from "./version";
|
|
18
18
|
import { ThemeProvider } from "./components/theme-context";
|
|
19
|
-
import type { InboxRow } from "./components/
|
|
19
|
+
import type { InboxRow } from "./components/thread-tree";
|
|
20
20
|
import { SettingsDialog } from "./components/SettingsDialog";
|
|
21
21
|
import { CompletionOverlay } from "./components/CompletionOverlay";
|
|
22
22
|
import { ThreadTree } from "./components/ThreadTree";
|
|
@@ -404,19 +404,19 @@ export function NoThreadShell(props: {
|
|
|
404
404
|
export function CompletionScreen(props: {
|
|
405
405
|
theme: Theme;
|
|
406
406
|
session: Thread;
|
|
407
|
-
|
|
407
|
+
message: MessageOutcome;
|
|
408
408
|
completion: { phase: "prompt" } | { phase: "counting"; remaining: number };
|
|
409
409
|
status: string;
|
|
410
410
|
onClose: () => void;
|
|
411
411
|
onBackToPlan: () => void;
|
|
412
412
|
onAlways: () => void;
|
|
413
413
|
}): React.ReactNode {
|
|
414
|
-
const { theme, session,
|
|
414
|
+
const { theme, session, message, completion, status, onClose, onBackToPlan, onAlways } = props;
|
|
415
415
|
|
|
416
416
|
return (
|
|
417
417
|
<ThemeProvider theme={theme}>
|
|
418
418
|
<CompletionOverlay
|
|
419
|
-
|
|
419
|
+
message={message}
|
|
420
420
|
completion={completion}
|
|
421
421
|
status={status}
|
|
422
422
|
onClose={onClose}
|
package/src/app-view-model.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
|
|
2
|
-
import type { Thread,
|
|
2
|
+
import type { Thread, MessageOutcome } from "@cueloop/schema";
|
|
3
3
|
import type { Mode } from "./intent-dispatch";
|
|
4
4
|
import type { Intent, KeyState } from "./keymap";
|
|
5
5
|
import type { Completion } from "./thread-controller";
|
|
@@ -85,7 +85,7 @@ export function buildHeaderItems(params: {
|
|
|
85
85
|
...(resolved
|
|
86
86
|
? [
|
|
87
87
|
{
|
|
88
|
-
label: `resolved: ${session.
|
|
88
|
+
label: `resolved: ${session.message!.outcome.replace("_", " ")}`,
|
|
89
89
|
tone: "green" as const,
|
|
90
90
|
},
|
|
91
91
|
]
|
|
@@ -144,7 +144,7 @@ export function buildSubmitConfirmState(
|
|
|
144
144
|
if (mode.type !== "submit") return null;
|
|
145
145
|
|
|
146
146
|
return {
|
|
147
|
-
|
|
147
|
+
message: mode.message,
|
|
148
148
|
summary: mode.summary,
|
|
149
149
|
quickActions: deps.quickActions,
|
|
150
150
|
viewedSummary:
|
|
@@ -155,8 +155,8 @@ export function buildSubmitConfirmState(
|
|
|
155
155
|
liveInput.current = summary;
|
|
156
156
|
setMode({ ...mode, summary });
|
|
157
157
|
},
|
|
158
|
-
|
|
159
|
-
onSubmit: () => dispatch({ type: "
|
|
158
|
+
onSelectMessage: (message: MessageOutcome) => setMode({ ...mode, message }),
|
|
159
|
+
onSubmit: () => dispatch({ type: "submitMessage" }),
|
|
160
160
|
onCancel: () => dispatch({ type: "closeOverlay" }),
|
|
161
161
|
};
|
|
162
162
|
}
|
|
@@ -15,7 +15,7 @@ const SESSION: Thread = {
|
|
|
15
15
|
artifact: { type: "plan", content: "# Plan\n", meta: {} },
|
|
16
16
|
revisions: [{ revision: 1, content: "# Plan\n", submittedAt: "2026-01-01T00:00:00Z" }],
|
|
17
17
|
annotations: [],
|
|
18
|
-
|
|
18
|
+
message: null,
|
|
19
19
|
status: "pending",
|
|
20
20
|
createdAt: "2026-01-01T00:00:00Z",
|
|
21
21
|
};
|
|
@@ -21,7 +21,7 @@ function planSession(): Thread {
|
|
|
21
21
|
artifact: { type: "plan", content: "# Plan\n", meta: {} },
|
|
22
22
|
revisions: [{ revision: 1, content: "# Plan\n", submittedAt: "2026-01-01T00:00:00Z" }],
|
|
23
23
|
annotations: [],
|
|
24
|
-
|
|
24
|
+
message: null,
|
|
25
25
|
status: "pending",
|
|
26
26
|
createdAt: "2026-01-01T00:00:00Z",
|
|
27
27
|
};
|
|
@@ -11,7 +11,7 @@ export const SessionHeader: Story = {
|
|
|
11
11
|
items={[
|
|
12
12
|
{ label: "cueloop", tone: "accent" },
|
|
13
13
|
{ label: "Migration Plan · rev 2", tone: "dim" },
|
|
14
|
-
{ label: "resolved:
|
|
14
|
+
{ label: "resolved: approved", tone: "green" },
|
|
15
15
|
]}
|
|
16
16
|
/>
|
|
17
17
|
),
|
|
@@ -18,7 +18,12 @@ const files: DiffFileContents[] = [
|
|
|
18
18
|
newContents: "c",
|
|
19
19
|
status: "added",
|
|
20
20
|
},
|
|
21
|
-
{
|
|
21
|
+
{
|
|
22
|
+
path: "packages/daemon/src/thread-review.ts",
|
|
23
|
+
oldContents: "d",
|
|
24
|
+
newContents: "",
|
|
25
|
+
status: "deleted",
|
|
26
|
+
},
|
|
22
27
|
{ path: "README.md", oldContents: "e", newContents: "f", status: "modified" },
|
|
23
28
|
];
|
|
24
29
|
|
|
@@ -10,7 +10,7 @@ const noop = () => {};
|
|
|
10
10
|
export const ApprovedPrompt: Story = {
|
|
11
11
|
render: () => (
|
|
12
12
|
<CompletionOverlay
|
|
13
|
-
|
|
13
|
+
message="approved"
|
|
14
14
|
completion={{ phase: "prompt" }}
|
|
15
15
|
status=""
|
|
16
16
|
onClose={noop}
|
|
@@ -24,7 +24,7 @@ export const ApprovedPrompt: Story = {
|
|
|
24
24
|
export const FeedbackCounting: Story = {
|
|
25
25
|
render: () => (
|
|
26
26
|
<CompletionOverlay
|
|
27
|
-
|
|
27
|
+
message="changes_requested"
|
|
28
28
|
completion={{ phase: "counting", remaining: 3 }}
|
|
29
29
|
status="exported to vault/cueloop/plan.md"
|
|
30
30
|
returnsTo="agent/worker-3"
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import React from "react";
|
|
8
|
-
import type {
|
|
8
|
+
import type { MessageOutcome } from "@cueloop/schema";
|
|
9
9
|
import type { Theme } from "../theme";
|
|
10
10
|
import { useComponentTheme } from "./theme-context";
|
|
11
11
|
import { Toolbar } from "./primitives/Toolbar";
|
|
12
12
|
import { Button } from "./primitives/Button";
|
|
13
13
|
|
|
14
14
|
export interface CompletionOverlayProps {
|
|
15
|
-
|
|
15
|
+
message: MessageOutcome;
|
|
16
16
|
completion: { phase: "prompt" } | { phase: "counting"; remaining: number };
|
|
17
17
|
/** Latest status line (e.g. the vault-export path) stays visible here. */
|
|
18
18
|
status: string;
|
|
@@ -25,7 +25,7 @@ export interface CompletionOverlayProps {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function CompletionOverlay({
|
|
28
|
-
|
|
28
|
+
message,
|
|
29
29
|
completion,
|
|
30
30
|
status,
|
|
31
31
|
returnsTo,
|
|
@@ -35,7 +35,7 @@ export function CompletionOverlay({
|
|
|
35
35
|
theme,
|
|
36
36
|
}: CompletionOverlayProps): React.ReactNode {
|
|
37
37
|
const tokens = useComponentTheme(theme);
|
|
38
|
-
const approved =
|
|
38
|
+
const approved = message === "approved";
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<box
|
|
@@ -8,21 +8,21 @@ export const meta: StoryMeta = { title: "Cards/ConfirmCard" };
|
|
|
8
8
|
|
|
9
9
|
const callbacks = {
|
|
10
10
|
onInput: () => {},
|
|
11
|
-
|
|
11
|
+
onSelectMessage: () => {},
|
|
12
12
|
onSubmit: () => {},
|
|
13
13
|
onCancel: () => {},
|
|
14
14
|
quickActions: DEFAULT_QUICK_ACTIONS,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export const ApproveDefault: Story = {
|
|
18
|
-
render: () => <ConfirmCard
|
|
18
|
+
render: () => <ConfirmCard message="approved" summary="" {...callbacks} />,
|
|
19
19
|
expectedColors: [DARK.green, DARK.accent],
|
|
20
20
|
size: { width: 40, height: 14 },
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const RequestChanges: Story = {
|
|
24
24
|
render: () => (
|
|
25
|
-
<ConfirmCard
|
|
25
|
+
<ConfirmCard message="changes_requested" summary="Tighten the steps." {...callbacks} />
|
|
26
26
|
),
|
|
27
27
|
expectedColors: [DARK.red],
|
|
28
28
|
size: { width: 40, height: 14 },
|
|
@@ -30,7 +30,7 @@ export const RequestChanges: Story = {
|
|
|
30
30
|
|
|
31
31
|
export const WithViewedSummary: Story = {
|
|
32
32
|
render: () => (
|
|
33
|
-
<ConfirmCard
|
|
33
|
+
<ConfirmCard message="approved" summary="" viewedSummary="2/3 files viewed" {...callbacks} />
|
|
34
34
|
),
|
|
35
35
|
expectedColors: [DARK.green, DARK.textDim],
|
|
36
36
|
size: { width: 40, height: 14 },
|