@cueloop/client 0.1.0-alpha.98 → 0.1.0-alpha.99
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 +11 -11
- package/src/annotations/components/AnnotationCards.tsx +2 -2
- package/src/annotations/use-annotation-surface.tsx +13 -2
- package/src/app/App.deleted-scroll.test.tsx +1 -0
- package/src/app/App.diff.test.tsx +1 -0
- package/src/app/App.identity.test.tsx +1 -0
- package/src/app/App.regression.test.tsx +5 -0
- package/src/app/App.share-dialog.test.tsx +5 -0
- package/src/app/App.submit-confirm.test.tsx +3 -0
- package/src/app/App.test.tsx +1 -0
- package/src/app/App.tsx +6 -0
- package/src/app/App.visual.test.tsx +2 -0
- package/src/app/App.workbench.test.tsx +53 -0
- package/src/app/app-screens.tsx +1 -0
- package/src/app/components/AppShell.tsx +1 -0
- package/src/app/run.ts +1 -0
- package/src/diff/components/AnnotatableFileView.test.tsx +2 -0
- package/src/diff/components/AnnotatableFileView.tsx +2 -0
- package/src/diff/components/DiffContentView.scroll.test.tsx +1 -0
- package/src/diff/components/DiffContentView.tsx +1 -0
- package/src/diff/diff-intraline.test.ts +1 -0
- package/src/diff/diff-intraline.ts +2 -0
- package/src/diff/split-diff.ts +2 -0
- package/src/integrations/components/ShareDialog.tsx +1 -0
- package/src/markdown/components/ThreadView.tsx +2 -0
- package/src/markdown/mark-runs.ts +1 -0
- package/src/markdown/markdown-highlight.ts +2 -0
- package/src/performance/otlp-trace.test.ts +3 -0
- package/src/performance/otlp-trace.ts +4 -0
- package/src/performance/perf-report.ts +1 -0
- package/src/performance/perf-timings.ts +1 -0
- package/src/settings/components/quick-actions-editor.test.tsx +3 -0
- package/src/stories/stories-app.tsx +3 -0
- package/src/stories/story.ts +2 -0
- package/src/terminal/pty.ts +1 -0
- package/src/thread/agent-transcript.ts +4 -0
- package/src/thread/components/AgentConfigControls.test.tsx +2 -0
- package/src/thread/components/AgentThreadPane.test.tsx +54 -5
- package/src/thread/components/AgentThreadPane.tsx +11 -0
- package/src/thread/components/ThreadFooter.test.tsx +2 -0
- package/src/thread/components/thread-tree.test.ts +3 -0
- package/src/thread/components/thread-tree.ts +4 -0
- package/src/thread/thread-controller.curate.test.ts +1 -0
- package/src/thread/thread-controller.live-diff.test.ts +7 -0
- package/src/thread/thread-controller.share.test.ts +1 -0
- package/src/thread/thread-controller.ts +8 -0
- package/src/thread/use-thread-agent.test.tsx +143 -0
- package/src/thread/use-thread-agent.ts +22 -12
- package/src/ui/components/PromptDialog.test.tsx +4 -0
- package/src/ui/components/RootOverlay.tsx +1 -0
- package/src/ui/components/primitives/Dialog.test.tsx +1 -0
- package/src/workbench/components/ChangesColumn.tsx +1 -0
- package/src/workbench/components/ChangesFileTree.test.tsx +2 -0
- package/src/workbench/components/FileContentsView.tsx +2 -0
- package/src/workbench/components/GridTabContent.tsx +1 -0
- package/src/workbench/components/ProjectTreeView.test.tsx +3 -0
- package/src/workbench/components/ProjectTreeView.tsx +1 -0
- package/src/workbench/components/editor-grid.test.ts +11 -0
- package/src/workbench/components/editor-grid.ts +10 -0
- package/src/workbench/use-changes-workbench.ts +5 -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.99",
|
|
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,18 +27,18 @@
|
|
|
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.
|
|
34
|
-
"@opentui/core": "^0.5.
|
|
35
|
-
"@opentui/keymap": "0.5.
|
|
36
|
-
"@opentui/react": "^0.5.
|
|
37
|
-
"@opentui/ssh": "0.5.
|
|
38
|
-
"@pierre/diffs": "^1.
|
|
30
|
+
"@cueloop/daemon": "0.1.0-alpha.99",
|
|
31
|
+
"@cueloop/extension-api": "0.1.0-alpha.99",
|
|
32
|
+
"@cueloop/integration-obsidian": "0.1.0-alpha.99",
|
|
33
|
+
"@cueloop/schema": "0.1.0-alpha.99",
|
|
34
|
+
"@opentui/core": "^0.5.14",
|
|
35
|
+
"@opentui/keymap": "0.5.14",
|
|
36
|
+
"@opentui/react": "^0.5.14",
|
|
37
|
+
"@opentui/ssh": "0.5.14",
|
|
38
|
+
"@pierre/diffs": "^1.5.1",
|
|
39
39
|
"@tanstack/virtual-core": "^3.17.11",
|
|
40
40
|
"diff": "^9",
|
|
41
|
-
"puppeteer-core": "^25.
|
|
41
|
+
"puppeteer-core": "^25.12.0",
|
|
42
42
|
"react": "^19.3.0",
|
|
43
43
|
"valibot": "^1.5.0",
|
|
44
44
|
"zustand": "^5.0.15"
|
|
@@ -62,7 +62,7 @@ export function Composer({
|
|
|
62
62
|
glyph: string | null;
|
|
63
63
|
tokens: Theme;
|
|
64
64
|
onSave: (body: string) => void;
|
|
65
|
-
onReady: () => void;
|
|
65
|
+
onReady: (readText: () => string) => void;
|
|
66
66
|
onInput: (text: string, caret: number) => void;
|
|
67
67
|
placeholder?: string;
|
|
68
68
|
agentEnabled?: boolean;
|
|
@@ -102,7 +102,7 @@ export function Composer({
|
|
|
102
102
|
editor.editBuffer.setSyntaxStyle(referenceStyleFor(tokens).style);
|
|
103
103
|
paintReferences(editor);
|
|
104
104
|
setRows(composeRowCount(editor.plainText, editor.width));
|
|
105
|
-
onReady();
|
|
105
|
+
onReady(() => editorRef.current?.plainText ?? "");
|
|
106
106
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
107
|
}, []);
|
|
108
108
|
|
|
@@ -255,6 +255,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
255
255
|
return { head: start, anchor: start };
|
|
256
256
|
});
|
|
257
257
|
const focusRequested = useEffectEvent(() => onFocusAnnotation?.(undefined));
|
|
258
|
+
|
|
258
259
|
useEffect(() => {
|
|
259
260
|
if (requestedBlock === undefined) return;
|
|
260
261
|
const next = { blockIndex: requestedBlock.blockIndex, char: 0 };
|
|
@@ -327,6 +328,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
327
328
|
const [caretOffset, setCaretOffset] = useState(0);
|
|
328
329
|
const [slashIndex, setSlashIndex] = useState(0);
|
|
329
330
|
const composerReady = useRef(false);
|
|
331
|
+
const readComposerText = useRef<(() => string) | null>(null);
|
|
330
332
|
const composeRef = useRef<ComposeState | null>(null);
|
|
331
333
|
const promptDraft = useRef("");
|
|
332
334
|
// every visual line registers its renderable so a drag can hit-test any
|
|
@@ -461,6 +463,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
461
463
|
if (observer) return onObserverBlocked?.("observer");
|
|
462
464
|
if (resolved) return onObserverBlocked?.("resolved");
|
|
463
465
|
composerReady.current = false;
|
|
466
|
+
readComposerText.current = null;
|
|
464
467
|
composeRef.current = state;
|
|
465
468
|
setCompose(state);
|
|
466
469
|
setDraft(state.seed);
|
|
@@ -469,6 +472,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
469
472
|
};
|
|
470
473
|
const closeCompose = (): void => {
|
|
471
474
|
composeRef.current = null;
|
|
475
|
+
readComposerText.current = null;
|
|
472
476
|
setCompose(null);
|
|
473
477
|
setDraft("");
|
|
474
478
|
setCaretOffset(0);
|
|
@@ -698,7 +702,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
698
702
|
if (isAgentInvokeKey(key, onInvoke)) {
|
|
699
703
|
key.preventDefault();
|
|
700
704
|
|
|
701
|
-
return saveComment(
|
|
705
|
+
return saveComment(readComposerText.current?.() ?? activeCompose.seed, true);
|
|
702
706
|
}
|
|
703
707
|
// the textarea owns every key while open; the view takes dismiss (which
|
|
704
708
|
// also releases the discussion focus), the slash palette, and pre-mount input
|
|
@@ -886,6 +890,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
886
890
|
if (activeCompose) return handleComposeKey(key, activeCompose);
|
|
887
891
|
if (isAgentInvokeKey(key, onInvoke)) {
|
|
888
892
|
key.preventDefault();
|
|
893
|
+
|
|
889
894
|
return onInvoke?.();
|
|
890
895
|
}
|
|
891
896
|
if (key.name === "escape") {
|
|
@@ -928,7 +933,10 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
928
933
|
tokens={tokens}
|
|
929
934
|
onSave={saveComment}
|
|
930
935
|
agentEnabled={Boolean(onInvoke)}
|
|
931
|
-
onReady={() =>
|
|
936
|
+
onReady={(readText) => {
|
|
937
|
+
readComposerText.current = readText;
|
|
938
|
+
composerReady.current = true;
|
|
939
|
+
}}
|
|
932
940
|
onInput={(text, caret) => {
|
|
933
941
|
setDraft(text);
|
|
934
942
|
setCaretOffset(caret);
|
|
@@ -1134,6 +1142,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
1134
1142
|
setCursor(blockIndex);
|
|
1135
1143
|
setCaret({ head: position, anchor: position });
|
|
1136
1144
|
});
|
|
1145
|
+
|
|
1137
1146
|
useEffect(() => {
|
|
1138
1147
|
movePromptDraft();
|
|
1139
1148
|
}, [source.count]);
|
|
@@ -1149,6 +1158,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
1149
1158
|
promptDraft.current = [promptRestoreRequest.text, newerDraft].filter(Boolean).join("\n");
|
|
1150
1159
|
focusPrompt(source.count - 1);
|
|
1151
1160
|
});
|
|
1161
|
+
|
|
1152
1162
|
useEffect(() => {
|
|
1153
1163
|
restorePrompt();
|
|
1154
1164
|
}, [promptRestoreRequest?.id, suspended]);
|
|
@@ -1161,6 +1171,7 @@ export function useAnnotationSurface(options: AnnotationSurfaceOptions): Annotat
|
|
|
1161
1171
|
if (composeRef.current || heldSpan || dragging.current || promptDraft.current) return;
|
|
1162
1172
|
focusPrompt(promptFocusRequest.blockIndex);
|
|
1163
1173
|
});
|
|
1174
|
+
|
|
1164
1175
|
useEffect(() => {
|
|
1165
1176
|
continueConversation();
|
|
1166
1177
|
}, [promptFocusRequest?.replyId, promptFocusRequest?.blockIndex, suspended]);
|
|
@@ -100,6 +100,7 @@ test("deleted file caret scrolls one row and stays at the file boundaries", asyn
|
|
|
100
100
|
|
|
101
101
|
for (let index = 0; index < 12; index++) {
|
|
102
102
|
const before = box.scrollTop;
|
|
103
|
+
|
|
103
104
|
await press(setup, "down");
|
|
104
105
|
steps.push(box.scrollTop - before);
|
|
105
106
|
const caretRow = setup
|
|
@@ -193,6 +193,7 @@ describe("diff review", () => {
|
|
|
193
193
|
test("pasting an image into a diff comment drops in an [Image #n] placeholder", async () => {
|
|
194
194
|
// Arrange - mark the added line and open the inline comment composer
|
|
195
195
|
const setup = await renderApp();
|
|
196
|
+
|
|
196
197
|
await dragText(setup, "new Map()", "new Map()", "new Map()".length);
|
|
197
198
|
await typeText(setup, "see ");
|
|
198
199
|
|
|
@@ -48,6 +48,7 @@ function accentTitle(setup: Awaited<ReturnType<typeof renderReadyApp>>): string
|
|
|
48
48
|
for (const line of setup.captureSpans().lines) {
|
|
49
49
|
for (const span of line.spans) {
|
|
50
50
|
const rgb = span.fg?.toInts();
|
|
51
|
+
|
|
51
52
|
if (
|
|
52
53
|
rgb &&
|
|
53
54
|
rgb[0] === ACCENT_DARK[0] &&
|
|
@@ -55,6 +56,7 @@ function accentTitle(setup: Awaited<ReturnType<typeof renderReadyApp>>): string
|
|
|
55
56
|
rgb[2] === ACCENT_DARK[2]
|
|
56
57
|
) {
|
|
57
58
|
const text = span.text.trim();
|
|
59
|
+
|
|
58
60
|
if (text.includes("review")) return text;
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -69,6 +71,7 @@ test("ctrl+q quits even when a menu holds the keyboard", async () => {
|
|
|
69
71
|
width: 120,
|
|
70
72
|
height: 32,
|
|
71
73
|
});
|
|
74
|
+
|
|
72
75
|
await waitForText(setup, "review");
|
|
73
76
|
|
|
74
77
|
// open the settings dialog from the top-left gear - it takes the keyboard
|
|
@@ -83,10 +86,12 @@ test("ctrl+q quits even when a menu holds the keyboard", async () => {
|
|
|
83
86
|
|
|
84
87
|
test("clicking a sidebar thread gives it the selected accent at once", async () => {
|
|
85
88
|
const setup = await renderReadyApp(<App home={home} />, { width: 120, height: 32 });
|
|
89
|
+
|
|
86
90
|
await waitForText(setup, "Second review");
|
|
87
91
|
|
|
88
92
|
// click whichever thread is not already the selected (accent) one
|
|
89
93
|
const target = accentTitle(setup)?.includes("Second") ? "First review" : "Second review";
|
|
94
|
+
|
|
90
95
|
await clickText(setup, target);
|
|
91
96
|
|
|
92
97
|
expect(accentTitle(setup)).toContain(target.split(" ")[0]!);
|
|
@@ -67,6 +67,7 @@ describe("share dialog", () => {
|
|
|
67
67
|
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
68
68
|
{ width: 120, height: 32 },
|
|
69
69
|
);
|
|
70
|
+
|
|
70
71
|
await waitForText(setup, "cueloop");
|
|
71
72
|
|
|
72
73
|
// Act
|
|
@@ -84,6 +85,7 @@ describe("share dialog", () => {
|
|
|
84
85
|
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
85
86
|
{ width: 120, height: 32 },
|
|
86
87
|
);
|
|
88
|
+
|
|
87
89
|
await waitForText(setup, "cueloop");
|
|
88
90
|
|
|
89
91
|
// Act: open the dialog, step into the body, activate "+ new link"
|
|
@@ -116,6 +118,7 @@ describe("share dialog", () => {
|
|
|
116
118
|
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
117
119
|
{ width: 120, height: 32 },
|
|
118
120
|
);
|
|
121
|
+
|
|
119
122
|
await waitForText(setup, "cueloop");
|
|
120
123
|
|
|
121
124
|
// Act: open the wizard, turn auth on but add no handles, then try to create
|
|
@@ -140,6 +143,7 @@ describe("share dialog", () => {
|
|
|
140
143
|
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
141
144
|
{ width: 120, height: 32 },
|
|
142
145
|
);
|
|
146
|
+
|
|
143
147
|
await waitForText(setup, "cueloop");
|
|
144
148
|
|
|
145
149
|
// Act + Assert: right arrow crosses into the body (its hint appears)
|
|
@@ -159,6 +163,7 @@ describe("share dialog", () => {
|
|
|
159
163
|
<App home={home} sessionId={session.id} shareTransport={shareTransport} />,
|
|
160
164
|
{ width: 120, height: 32 },
|
|
161
165
|
);
|
|
166
|
+
|
|
162
167
|
await waitForText(setup, "cueloop");
|
|
163
168
|
|
|
164
169
|
// Act
|
|
@@ -111,6 +111,7 @@ describe("send message confirm", () => {
|
|
|
111
111
|
.captureCharFrame()
|
|
112
112
|
.split("\n")
|
|
113
113
|
.find((row) => row.includes("[comment] [approve] [changes]"));
|
|
114
|
+
|
|
114
115
|
await press(setup, "right");
|
|
115
116
|
const commentSelector = setup
|
|
116
117
|
.captureCharFrame()
|
|
@@ -220,6 +221,7 @@ describe("send message confirm", () => {
|
|
|
220
221
|
// Arrange
|
|
221
222
|
seedAnnotations(1);
|
|
222
223
|
const setup = await renderApp();
|
|
224
|
+
|
|
223
225
|
await pressKey(setup, "RETURN", { meta: true });
|
|
224
226
|
await waitForText(setup, "[approve]");
|
|
225
227
|
|
|
@@ -233,6 +235,7 @@ describe("send message confirm", () => {
|
|
|
233
235
|
test("pasting an image into the summary drops in an [Image #n] placeholder", async () => {
|
|
234
236
|
// Arrange
|
|
235
237
|
const setup = await renderApp();
|
|
238
|
+
|
|
236
239
|
await pressKey(setup, "RETURN", { meta: true });
|
|
237
240
|
await waitForText(setup, "[approve]");
|
|
238
241
|
|
package/src/app/App.test.tsx
CHANGED
|
@@ -378,6 +378,7 @@ describe("no-thread shell", () => {
|
|
|
378
378
|
// unique "rename" row - the star action sits directly above it
|
|
379
379
|
await waitForText(setup, "rename");
|
|
380
380
|
const rename = locateText(setup, "rename");
|
|
381
|
+
|
|
381
382
|
await setup.mockMouse.click(rename.column, rename.row - 1);
|
|
382
383
|
|
|
383
384
|
// Assert - a Starred section now holds the thread
|
package/src/app/App.tsx
CHANGED
|
@@ -376,6 +376,7 @@ function ProjectPanelBody(props: {
|
|
|
376
376
|
/>
|
|
377
377
|
);
|
|
378
378
|
}
|
|
379
|
+
|
|
379
380
|
return (
|
|
380
381
|
<ProjectTreeView
|
|
381
382
|
key={props.diffSourceKey}
|
|
@@ -617,6 +618,7 @@ export function App({
|
|
|
617
618
|
// A shared plan you own polls for collaborator notes while it is open; the
|
|
618
619
|
// merge refreshes through the normal event path. Stops on leave.
|
|
619
620
|
const threadShared = isSharedThread(session);
|
|
621
|
+
|
|
620
622
|
useEffect(() => {
|
|
621
623
|
if (!isOwner || !threadShared) return;
|
|
622
624
|
|
|
@@ -655,6 +657,7 @@ export function App({
|
|
|
655
657
|
const [focusedPane, setFocusedPane] = useState<FocusPane>(() =>
|
|
656
658
|
initialFocusedPane(sessionId, workbench.zoomed, layout),
|
|
657
659
|
);
|
|
660
|
+
|
|
658
661
|
// only the session view persists from here; the bare shell owns its own (NoThreadShell), so this
|
|
659
662
|
// workbench stays inactive with no session and never clobbers what the shell saved
|
|
660
663
|
useRememberLayout(
|
|
@@ -765,6 +768,7 @@ export function App({
|
|
|
765
768
|
};
|
|
766
769
|
const syncGithubIdentity = (): void => {
|
|
767
770
|
const generation = (identityGenerationRef.current += 1);
|
|
771
|
+
|
|
768
772
|
void resolveGithubIdentity().then((github) => {
|
|
769
773
|
if (identityGenerationRef.current !== generation) return;
|
|
770
774
|
if (!github) return controller.setStatus("GitHub not connected - run gh auth login");
|
|
@@ -850,6 +854,7 @@ export function App({
|
|
|
850
854
|
() => (session && isDiff ? commentCountsByFile(session, rows) : undefined),
|
|
851
855
|
[session, isDiff, rows],
|
|
852
856
|
);
|
|
857
|
+
|
|
853
858
|
// entering a diff opens the right region in changed-files mode; a plan or reply opens it closed
|
|
854
859
|
workbench.syncSession(session?.id, isDiff);
|
|
855
860
|
// plans, replies, and the default (markdown) prototype open in the thread view, diffs in the
|
|
@@ -989,6 +994,7 @@ export function App({
|
|
|
989
994
|
workbench.changesOpen,
|
|
990
995
|
workbench.projectOpen,
|
|
991
996
|
);
|
|
997
|
+
|
|
992
998
|
useEffect(() => {
|
|
993
999
|
// the bare shell drives its panes from its own workbench, so only reconcile against this one
|
|
994
1000
|
if (!session) return;
|
|
@@ -33,6 +33,7 @@ function hasSpanColor(
|
|
|
33
33
|
for (const span of line.spans) {
|
|
34
34
|
if (!span.text.includes(needle) || !span.fg) continue;
|
|
35
35
|
const [red, green, blue] = span.fg.toInts();
|
|
36
|
+
|
|
36
37
|
if (red === color[0] && green === color[1] && blue === color[2]) return true;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -47,6 +48,7 @@ for (const mode of ["dark", "light"] as const) {
|
|
|
47
48
|
width: 120,
|
|
48
49
|
height: 32,
|
|
49
50
|
});
|
|
51
|
+
|
|
50
52
|
await waitForText(setup, "Second review");
|
|
51
53
|
|
|
52
54
|
await clickText(setup, "Second review");
|