@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.
Files changed (60) hide show
  1. package/package.json +11 -11
  2. package/src/annotations/components/AnnotationCards.tsx +2 -2
  3. package/src/annotations/use-annotation-surface.tsx +13 -2
  4. package/src/app/App.deleted-scroll.test.tsx +1 -0
  5. package/src/app/App.diff.test.tsx +1 -0
  6. package/src/app/App.identity.test.tsx +1 -0
  7. package/src/app/App.regression.test.tsx +5 -0
  8. package/src/app/App.share-dialog.test.tsx +5 -0
  9. package/src/app/App.submit-confirm.test.tsx +3 -0
  10. package/src/app/App.test.tsx +1 -0
  11. package/src/app/App.tsx +6 -0
  12. package/src/app/App.visual.test.tsx +2 -0
  13. package/src/app/App.workbench.test.tsx +53 -0
  14. package/src/app/app-screens.tsx +1 -0
  15. package/src/app/components/AppShell.tsx +1 -0
  16. package/src/app/run.ts +1 -0
  17. package/src/diff/components/AnnotatableFileView.test.tsx +2 -0
  18. package/src/diff/components/AnnotatableFileView.tsx +2 -0
  19. package/src/diff/components/DiffContentView.scroll.test.tsx +1 -0
  20. package/src/diff/components/DiffContentView.tsx +1 -0
  21. package/src/diff/diff-intraline.test.ts +1 -0
  22. package/src/diff/diff-intraline.ts +2 -0
  23. package/src/diff/split-diff.ts +2 -0
  24. package/src/integrations/components/ShareDialog.tsx +1 -0
  25. package/src/markdown/components/ThreadView.tsx +2 -0
  26. package/src/markdown/mark-runs.ts +1 -0
  27. package/src/markdown/markdown-highlight.ts +2 -0
  28. package/src/performance/otlp-trace.test.ts +3 -0
  29. package/src/performance/otlp-trace.ts +4 -0
  30. package/src/performance/perf-report.ts +1 -0
  31. package/src/performance/perf-timings.ts +1 -0
  32. package/src/settings/components/quick-actions-editor.test.tsx +3 -0
  33. package/src/stories/stories-app.tsx +3 -0
  34. package/src/stories/story.ts +2 -0
  35. package/src/terminal/pty.ts +1 -0
  36. package/src/thread/agent-transcript.ts +4 -0
  37. package/src/thread/components/AgentConfigControls.test.tsx +2 -0
  38. package/src/thread/components/AgentThreadPane.test.tsx +54 -5
  39. package/src/thread/components/AgentThreadPane.tsx +11 -0
  40. package/src/thread/components/ThreadFooter.test.tsx +2 -0
  41. package/src/thread/components/thread-tree.test.ts +3 -0
  42. package/src/thread/components/thread-tree.ts +4 -0
  43. package/src/thread/thread-controller.curate.test.ts +1 -0
  44. package/src/thread/thread-controller.live-diff.test.ts +7 -0
  45. package/src/thread/thread-controller.share.test.ts +1 -0
  46. package/src/thread/thread-controller.ts +8 -0
  47. package/src/thread/use-thread-agent.test.tsx +143 -0
  48. package/src/thread/use-thread-agent.ts +22 -12
  49. package/src/ui/components/PromptDialog.test.tsx +4 -0
  50. package/src/ui/components/RootOverlay.tsx +1 -0
  51. package/src/ui/components/primitives/Dialog.test.tsx +1 -0
  52. package/src/workbench/components/ChangesColumn.tsx +1 -0
  53. package/src/workbench/components/ChangesFileTree.test.tsx +2 -0
  54. package/src/workbench/components/FileContentsView.tsx +2 -0
  55. package/src/workbench/components/GridTabContent.tsx +1 -0
  56. package/src/workbench/components/ProjectTreeView.test.tsx +3 -0
  57. package/src/workbench/components/ProjectTreeView.tsx +1 -0
  58. package/src/workbench/components/editor-grid.test.ts +11 -0
  59. package/src/workbench/components/editor-grid.ts +10 -0
  60. 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.98",
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.98",
31
- "@cueloop/extension-api": "0.1.0-alpha.98",
32
- "@cueloop/integration-obsidian": "0.1.0-alpha.98",
33
- "@cueloop/schema": "0.1.0-alpha.98",
34
- "@opentui/core": "^0.5.12",
35
- "@opentui/keymap": "0.5.12",
36
- "@opentui/react": "^0.5.12",
37
- "@opentui/ssh": "0.5.12",
38
- "@pierre/diffs": "^1.4.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.11.0",
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(composerReady.current ? composeTextRef.current : activeCompose.seed, true);
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={() => (composerReady.current = true)}
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
 
@@ -34,6 +34,7 @@ beforeEach(() => {
34
34
  meta: { title: "Migration Plan", planPath: "plan.md" },
35
35
  },
36
36
  });
37
+
37
38
  sessionId = session.id;
38
39
  });
39
40
  afterEach(() => {
@@ -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
 
@@ -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");