@cueloop/client 0.1.0-alpha.82 → 0.1.0-alpha.84
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 +4 -3
- package/src/App.export.test.tsx +1 -1
- package/src/App.readonly.test.tsx +1 -1
- package/src/App.submit-confirm.test.tsx +59 -17
- package/src/App.test.tsx +77 -3
- package/src/App.tsx +15 -7
- package/src/App.walk.test.tsx +1 -1
- package/src/components/ConfirmCard.tsx +11 -14
- package/src/components/DiffContentView.tsx +3 -2
- package/src/components/ProjectTreeView.test.tsx +17 -1
- package/src/components/ProjectTreeView.tsx +16 -2
- package/src/components/ThreadView.selection.test.tsx +31 -2
- package/src/components/ThreadView.tsx +4 -3
- package/src/components/__snapshots__/stories.test.tsx.snap +23 -23
- package/src/config.ts +2 -2
- package/src/intent-dispatch.test.ts +2 -2
- package/src/intent-dispatch.ts +2 -4
- package/src/keymap.ts +0 -2
- package/src/thread-controller.curate.test.ts +36 -10
- package/src/thread-controller.switch-cache.test.ts +1 -1
- package/src/thread-controller.ts +175 -46
- package/src/tree-view.ts +6 -1
- package/src/use-annotation-surface.tsx +72 -12
- package/src/use-terminal-virtualizer.ts +26 -0
- package/src/view-plan.test.ts +66 -1
- package/src/view-plan.ts +281 -9
|
@@ -363,13 +363,13 @@ exports[`stories catalog Overlays/CompletionOverlay/FeedbackCounting renders, sn
|
|
|
363
363
|
`;
|
|
364
364
|
|
|
365
365
|
exports[`stories catalog Cards/ConfirmCard/ApproveDefault renders, snapshots, and carries its colors: Cards/ConfirmCard/ApproveDefault 1`] = `
|
|
366
|
-
"
|
|
367
|
-
│
|
|
368
|
-
│
|
|
369
|
-
│ summary for the agent (
|
|
370
|
-
│
|
|
371
|
-
│ send cancel
|
|
372
|
-
|
|
366
|
+
"┌────────────────────────────────────┐
|
|
367
|
+
│ [comment] [approve] [changes] │
|
|
368
|
+
│ │
|
|
369
|
+
│ summary for the agent (optional) │
|
|
370
|
+
│ │
|
|
371
|
+
│ send cancel │
|
|
372
|
+
└────────────────────────────────────┘
|
|
373
373
|
|
|
374
374
|
|
|
375
375
|
|
|
@@ -381,13 +381,13 @@ exports[`stories catalog Cards/ConfirmCard/ApproveDefault renders, snapshots, an
|
|
|
381
381
|
`;
|
|
382
382
|
|
|
383
383
|
exports[`stories catalog Cards/ConfirmCard/RequestChanges renders, snapshots, and carries its colors: Cards/ConfirmCard/RequestChanges 1`] = `
|
|
384
|
-
"
|
|
385
|
-
│
|
|
386
|
-
│
|
|
387
|
-
│ Tighten the steps.
|
|
388
|
-
│
|
|
389
|
-
│ send cancel
|
|
390
|
-
|
|
384
|
+
"┌────────────────────────────────────┐
|
|
385
|
+
│ [comment] [approve] [changes] │
|
|
386
|
+
│ │
|
|
387
|
+
│ Tighten the steps. │
|
|
388
|
+
│ │
|
|
389
|
+
│ send cancel │
|
|
390
|
+
└────────────────────────────────────┘
|
|
391
391
|
|
|
392
392
|
|
|
393
393
|
|
|
@@ -399,15 +399,15 @@ exports[`stories catalog Cards/ConfirmCard/RequestChanges renders, snapshots, an
|
|
|
399
399
|
`;
|
|
400
400
|
|
|
401
401
|
exports[`stories catalog Cards/ConfirmCard/WithViewedSummary renders, snapshots, and carries its colors: Cards/ConfirmCard/WithViewedSummary 1`] = `
|
|
402
|
-
"
|
|
403
|
-
│ 2/3 files viewed
|
|
404
|
-
│
|
|
405
|
-
│
|
|
406
|
-
│
|
|
407
|
-
│ summary for the agent (
|
|
408
|
-
│
|
|
409
|
-
│ send cancel
|
|
410
|
-
|
|
402
|
+
"┌────────────────────────────────────┐
|
|
403
|
+
│ 2/3 files viewed │
|
|
404
|
+
│ │
|
|
405
|
+
│ [comment] [approve] [changes] │
|
|
406
|
+
│ │
|
|
407
|
+
│ summary for the agent (optional) │
|
|
408
|
+
│ │
|
|
409
|
+
│ send cancel │
|
|
410
|
+
└────────────────────────────────────┘
|
|
411
411
|
|
|
412
412
|
|
|
413
413
|
|
package/src/config.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { OBSIDIAN_DEFAULTS, type ObsidianConfig } from "@cueloop/integration-obsidian";
|
|
12
|
-
import type
|
|
12
|
+
import { MESSAGE_OUTCOMES, type MessageOutcome } from "@cueloop/schema";
|
|
13
13
|
import { writeFileSync, mkdirSync } from "node:fs";
|
|
14
14
|
import { dirname } from "node:path";
|
|
15
15
|
import * as v from "valibot";
|
|
@@ -213,7 +213,7 @@ const UiSchema = v.object({
|
|
|
213
213
|
editor: v.fallback(v.optional(v.string()), undefined),
|
|
214
214
|
theme: v.fallback(v.optional(v.string()), undefined),
|
|
215
215
|
diff_view: v.fallback(v.optional(v.picklist(["split", "stacked", "unified"])), undefined),
|
|
216
|
-
default_message: v.fallback(v.optional(v.picklist(
|
|
216
|
+
default_message: v.fallback(v.optional(v.picklist(MESSAGE_OUTCOMES)), undefined),
|
|
217
217
|
pins: v.fallback(v.optional(v.array(v.string())), undefined),
|
|
218
218
|
layout: v.fallback(
|
|
219
219
|
v.optional(
|
|
@@ -276,7 +276,7 @@ describe("cycleMessage", () => {
|
|
|
276
276
|
describe("marker-actions popover", () => {
|
|
277
277
|
const span = { displayIndex: 3, wordIndex: 0, wordEnd: 0, start: 2, end: 9 };
|
|
278
278
|
|
|
279
|
-
test("spanCut cuts the
|
|
279
|
+
test("spanCut cuts only the marked character range and closes the popover", () => {
|
|
280
280
|
// Arrange
|
|
281
281
|
const deps = makeDeps({ mode: { type: "span", span } });
|
|
282
282
|
const dispatch = createIntentDispatch(deps);
|
|
@@ -285,7 +285,7 @@ describe("marker-actions popover", () => {
|
|
|
285
285
|
dispatch({ type: "spanCut" });
|
|
286
286
|
|
|
287
287
|
// Assert
|
|
288
|
-
expect(deps.controller.cut).toHaveBeenCalledWith(3);
|
|
288
|
+
expect(deps.controller.cut).toHaveBeenCalledWith(3, 2, 9);
|
|
289
289
|
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
290
290
|
});
|
|
291
291
|
|
package/src/intent-dispatch.ts
CHANGED
|
@@ -8,14 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
|
|
11
|
-
import { isAddressed, type Thread, type MessageOutcome } from "@cueloop/schema";
|
|
11
|
+
import { isAddressed, MESSAGE_OUTCOMES, type Thread, type MessageOutcome } from "@cueloop/schema";
|
|
12
12
|
import { displayText, spanKey, startSpan, type DisplayBlock, type SpanState } from "./view-plan";
|
|
13
13
|
import type { DiffRow } from "./view-diff";
|
|
14
14
|
import type { ReviewController } from "./thread-controller";
|
|
15
15
|
import type { Intent } from "./keymap";
|
|
16
16
|
import type { TreeRow } from "./tree-view";
|
|
17
17
|
import { quickActionBody, type QuickAction } from "./config";
|
|
18
|
-
import { MESSAGE_OUTCOMES } from "./components/ConfirmCard";
|
|
19
18
|
|
|
20
19
|
/** Which pane of the session tree / review the keyboard grammar is aimed at. */
|
|
21
20
|
export type RailTab = "review" | "tree";
|
|
@@ -256,11 +255,10 @@ function handleSpanKey(intent: IntentOfType<"spanKey">, deps: IntentDispatchDeps
|
|
|
256
255
|
}
|
|
257
256
|
|
|
258
257
|
function handleSpanCut(_intent: IntentOfType<"spanCut">, deps: IntentDispatchDeps): void {
|
|
259
|
-
// the block the span sits in, cut whole (partial-span cut is not modeled)
|
|
260
258
|
const { mode } = deps;
|
|
261
259
|
|
|
262
260
|
if (mode.type === "span") {
|
|
263
|
-
deps.controller.cut(mode.span.displayIndex);
|
|
261
|
+
deps.controller.cut(mode.span.displayIndex, mode.span.start, mode.span.end);
|
|
264
262
|
deps.setMode({ type: "normal" });
|
|
265
263
|
}
|
|
266
264
|
}
|
package/src/keymap.ts
CHANGED
|
@@ -315,8 +315,6 @@ function spanGrammar(state: KeyState, name: string): Intent[] {
|
|
|
315
315
|
if (state.resolved) return status("review submitted - read-only");
|
|
316
316
|
if (name === "c") return [{ type: "openCompose", kind: "comment", from: "span" }];
|
|
317
317
|
if (name === "a") return [{ type: "openSpanActions" }];
|
|
318
|
-
// partial-span cut is not in the working-copy model, so cut removes the
|
|
319
|
-
// whole block the span sits in; owner-only, like plan cut
|
|
320
318
|
if (state.canEditPlan === false) return [];
|
|
321
319
|
|
|
322
320
|
return [{ type: "spanCut" }];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
|
+
applyTextCuts,
|
|
3
4
|
cutBlock,
|
|
4
5
|
parseBlocks,
|
|
5
6
|
restoreBlock,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
type DiffFileContents,
|
|
8
9
|
type HunkRejection,
|
|
9
10
|
type Thread,
|
|
11
|
+
type TextCut,
|
|
10
12
|
} from "@cueloop/schema";
|
|
11
13
|
import { curateDiff } from "@cueloop/daemon/curate";
|
|
12
14
|
import type { ThreadClient } from "@cueloop/daemon/client";
|
|
@@ -48,6 +50,7 @@ function diffSession(files?: DiffFileContents[]): Thread {
|
|
|
48
50
|
|
|
49
51
|
interface WorkingCopySink {
|
|
50
52
|
workingCopy?: string;
|
|
53
|
+
textCuts?: TextCut[];
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
const unimplemented = (member: string) => () =>
|
|
@@ -65,11 +68,15 @@ function fakeClient(initial: Thread, sink: WorkingCopySink): ThreadClient {
|
|
|
65
68
|
sessionComment: unimplemented("sessionComment"),
|
|
66
69
|
sessionAnnotate: unimplemented("sessionAnnotate"),
|
|
67
70
|
sessionRemoveAnnotation: unimplemented("sessionRemoveAnnotation"),
|
|
68
|
-
sessionSetWorkingCopy: mock(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
sessionSetWorkingCopy: mock(
|
|
72
|
+
async (_id: string, content: string | undefined, textCuts?: TextCut[]) => {
|
|
73
|
+
sink.workingCopy = content;
|
|
74
|
+
sink.textCuts = textCuts;
|
|
75
|
+
session = { ...session, workingCopy: content, textCuts };
|
|
76
|
+
|
|
77
|
+
return session;
|
|
78
|
+
},
|
|
79
|
+
),
|
|
73
80
|
// the daemon's block primitives, stood in for with the same pure helpers it uses
|
|
74
81
|
sessionCutBlock: mock(async (_id: string, blockIndex: number) => {
|
|
75
82
|
const working = session.workingCopy ?? session.artifact.content;
|
|
@@ -154,7 +161,7 @@ describe("diff hunk curation", () => {
|
|
|
154
161
|
await tick();
|
|
155
162
|
|
|
156
163
|
// Assert
|
|
157
|
-
expect(controller.getSnapshot().
|
|
164
|
+
expect(controller.getSnapshot().toast).toBeNull();
|
|
158
165
|
expect(sink.workingCopy).toBe("");
|
|
159
166
|
});
|
|
160
167
|
|
|
@@ -181,7 +188,7 @@ describe("diff hunk curation", () => {
|
|
|
181
188
|
await tick();
|
|
182
189
|
|
|
183
190
|
// Assert - no decisions left, so the working copy reverts to undefined
|
|
184
|
-
expect(controller.getSnapshot().
|
|
191
|
+
expect(controller.getSnapshot().toast).toBeNull();
|
|
185
192
|
expect(sink.workingCopy).toBeUndefined();
|
|
186
193
|
expect(controller.rejectedRows().size).toBe(0);
|
|
187
194
|
});
|
|
@@ -195,7 +202,7 @@ describe("diff hunk curation", () => {
|
|
|
195
202
|
await tick();
|
|
196
203
|
|
|
197
204
|
// Assert - the single hunk is the whole diff, so nothing remains
|
|
198
|
-
expect(controller.getSnapshot().
|
|
205
|
+
expect(controller.getSnapshot().toast).toBeNull();
|
|
199
206
|
expect(sink.workingCopy).toBe("");
|
|
200
207
|
});
|
|
201
208
|
|
|
@@ -249,7 +256,7 @@ describe("diff hunk curation", () => {
|
|
|
249
256
|
await tick();
|
|
250
257
|
|
|
251
258
|
// Assert - the list empties and the working copy reverts to the full diff
|
|
252
|
-
expect(controller.getSnapshot().
|
|
259
|
+
expect(controller.getSnapshot().toast).toBeNull();
|
|
253
260
|
expect(controller.curationItems().length).toBe(0);
|
|
254
261
|
expect(sink.workingCopy).toBeUndefined();
|
|
255
262
|
expect(controller.rejectedRows().size).toBe(0);
|
|
@@ -355,7 +362,7 @@ describe("plan cut removals", () => {
|
|
|
355
362
|
await tick();
|
|
356
363
|
|
|
357
364
|
// Assert - restoring the only cut round-trips to the submitted revision
|
|
358
|
-
expect(controller.getSnapshot().
|
|
365
|
+
expect(controller.getSnapshot().toast).toBeNull();
|
|
359
366
|
expect(sink.workingCopy).toBeUndefined();
|
|
360
367
|
});
|
|
361
368
|
|
|
@@ -373,6 +380,25 @@ describe("plan cut removals", () => {
|
|
|
373
380
|
expect(client.sessionCutBlock).toHaveBeenNthCalledWith(2, "ses_plan", 1);
|
|
374
381
|
expect(sink.workingCopy).toBe("# Title\n");
|
|
375
382
|
});
|
|
383
|
+
|
|
384
|
+
test("a whole-block Cut composes with an earlier exact Cut", async () => {
|
|
385
|
+
const start = PLAN_CONTENT.indexOf("paragraph.");
|
|
386
|
+
const end = PLAN_CONTENT.indexOf("Second") + "Sec".length;
|
|
387
|
+
const textCuts = [{ start, end, quote: PLAN_CONTENT.slice(start, end) }];
|
|
388
|
+
const session = {
|
|
389
|
+
...planSession(applyTextCuts(PLAN_CONTENT, textCuts)),
|
|
390
|
+
textCuts,
|
|
391
|
+
};
|
|
392
|
+
const { controller, client, sink } = await connected(session);
|
|
393
|
+
|
|
394
|
+
controller.cut(1);
|
|
395
|
+
await tick();
|
|
396
|
+
|
|
397
|
+
expect(client.sessionSetWorkingCopy).toHaveBeenCalledTimes(1);
|
|
398
|
+
expect(client.sessionCutBlock).not.toHaveBeenCalled();
|
|
399
|
+
expect(sink.textCuts).toHaveLength(1);
|
|
400
|
+
expect(sink.workingCopy).toBe("# Title\nond paragraph.\n");
|
|
401
|
+
});
|
|
376
402
|
});
|
|
377
403
|
|
|
378
404
|
// a file whose hunk touches line 2 only, so lines 1/3/4 are unchanged tail/context
|
|
@@ -141,7 +141,7 @@ describe("switching threads keeps the cached projection curatable", () => {
|
|
|
141
141
|
controller.toggleRejectChange(4);
|
|
142
142
|
await tick();
|
|
143
143
|
|
|
144
|
-
expect(controller.getSnapshot().
|
|
144
|
+
expect(controller.getSnapshot().toast).toBeNull();
|
|
145
145
|
expect(controller.getSnapshot().session?.workingCopy).toBe("");
|
|
146
146
|
});
|
|
147
147
|
|