@cueloop/client 0.1.0-alpha.33 → 0.1.0-alpha.35

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 (94) hide show
  1. package/package.json +27 -26
  2. package/src/App.autoclose.test.tsx +7 -4
  3. package/src/App.diff.test.tsx +130 -4
  4. package/src/App.export.test.tsx +22 -4
  5. package/src/App.plan-review.test.tsx +127 -27
  6. package/src/App.readonly.test.tsx +31 -8
  7. package/src/App.rendering.test.tsx +12 -3
  8. package/src/App.review-panel.test.tsx +11 -3
  9. package/src/App.submit-confirm.test.tsx +12 -7
  10. package/src/App.test.tsx +46 -9
  11. package/src/App.toast.test.tsx +10 -3
  12. package/src/App.tsx +400 -100
  13. package/src/App.walk.test.tsx +30 -10
  14. package/src/attribution.ts +8 -2
  15. package/src/clipboard.ts +5 -1
  16. package/src/components/AnnotationCard.stories.tsx +28 -9
  17. package/src/components/AnnotationCard.tsx +53 -38
  18. package/src/components/Breadcrumb.tsx +7 -1
  19. package/src/components/CodeBlock.stories.tsx +19 -2
  20. package/src/components/CodeBlock.tsx +24 -9
  21. package/src/components/CompletionOverlay.tsx +13 -3
  22. package/src/components/ConfirmCard.stories.tsx +22 -3
  23. package/src/components/ConfirmCard.tsx +22 -5
  24. package/src/components/ConfirmDialog.tsx +9 -1
  25. package/src/components/DiffSheet.scroll.test.tsx +123 -0
  26. package/src/components/DiffSheet.stories.tsx +18 -2
  27. package/src/components/DiffSheet.syntax.test.tsx +62 -0
  28. package/src/components/DiffSheet.tsx +184 -98
  29. package/src/components/InboxList.stories.tsx +16 -3
  30. package/src/components/InboxList.tsx +17 -10
  31. package/src/components/KeybindsDialog.tsx +19 -3
  32. package/src/components/MarkerPopover.stories.tsx +58 -0
  33. package/src/components/MarkerPopover.tsx +102 -0
  34. package/src/components/MenuBar.stories.tsx +5 -1
  35. package/src/components/MenuBar.tsx +18 -2
  36. package/src/components/PlanSheet.stories.tsx +34 -2
  37. package/src/components/PlanSheet.tsx +141 -32
  38. package/src/components/PromptDialog.tsx +18 -2
  39. package/src/components/ReviewPanel.stories.tsx +13 -3
  40. package/src/components/ReviewPanel.tsx +8 -4
  41. package/src/components/ReviewRail.stories.tsx +145 -72
  42. package/src/components/ReviewRail.tsx +177 -38
  43. package/src/components/SettingsDialog.stories.tsx +51 -3
  44. package/src/components/SettingsDialog.tsx +8 -3
  45. package/src/components/SettingsRows.tsx +19 -3
  46. package/src/components/Toast.tsx +17 -2
  47. package/src/components/WalkWizard.stories.tsx +13 -2
  48. package/src/components/WalkWizard.tsx +9 -3
  49. package/src/components/__snapshots__/stories.test.tsx.snap +281 -83
  50. package/src/components/diff-sheet-layout.test.ts +127 -0
  51. package/src/components/diff-sheet-layout.ts +167 -0
  52. package/src/components/primitives/Button.tsx +5 -1
  53. package/src/components/primitives/Dialog.tsx +9 -1
  54. package/src/components/primitives/Tabs.tsx +24 -4
  55. package/src/components/stories-app.tsx +34 -6
  56. package/src/components/story-fixtures.ts +11 -3
  57. package/src/components/story.ts +3 -1
  58. package/src/components/syntax-highlight.ts +47 -12
  59. package/src/components/theme-context.stories.tsx +6 -1
  60. package/src/components/theme-context.tsx +7 -1
  61. package/src/config.test.ts +178 -2
  62. package/src/config.ts +121 -23
  63. package/src/diff-hunk-curate.test.ts +315 -0
  64. package/src/diff-hunk-curate.ts +233 -0
  65. package/src/diff-intraline.test.ts +164 -0
  66. package/src/diff-intraline.ts +132 -0
  67. package/src/diff-syntax.test.ts +96 -0
  68. package/src/diff-syntax.ts +154 -0
  69. package/src/editor.test.ts +13 -4
  70. package/src/editor.ts +31 -5
  71. package/src/herdr.ts +8 -2
  72. package/src/index.ts +9 -1
  73. package/src/integrations.ts +3 -1
  74. package/src/intent-dispatch.test.ts +193 -9
  75. package/src/intent-dispatch.ts +151 -18
  76. package/src/key-bindings.test.ts +19 -9
  77. package/src/key-bindings.ts +36 -14
  78. package/src/keymap.test.ts +171 -26
  79. package/src/keymap.ts +83 -16
  80. package/src/review-panel.test.ts +3 -1
  81. package/src/review-panel.ts +9 -2
  82. package/src/serve.test.ts +11 -3
  83. package/src/session-controller.curate.test.ts +253 -0
  84. package/src/session-controller.share.test.ts +65 -17
  85. package/src/session-controller.ts +301 -26
  86. package/src/share.ts +41 -8
  87. package/src/test-support.ts +16 -6
  88. package/src/theme-presets.ts +168 -0
  89. package/src/theme.ts +1 -3
  90. package/src/view-diff.ts +15 -4
  91. package/src/view-plan.test.ts +40 -6
  92. package/src/view-plan.ts +71 -28
  93. package/src/walk.test.ts +7 -2
  94. package/src/walk.ts +7 -2
@@ -51,7 +51,10 @@ afterEach(() => {
51
51
  });
52
52
 
53
53
  async function frame(): Promise<string> {
54
- const setup = await testRender(<App home={home} sessionId={session.id} />, { width: 120, height: 36 });
54
+ const setup = await testRender(<App home={home} sessionId={session.id} />, {
55
+ width: 120,
56
+ height: 36,
57
+ });
55
58
  await waitForText(setup, "Render Plan");
56
59
  // async highlights settle within the visual-idle wait
57
60
  await setup.waitForVisualIdle();
@@ -112,11 +115,17 @@ describe("block spacing", () => {
112
115
  workspace: { repoRoot: "/repo", branch: "main" },
113
116
  artifact: { type: "plan", content: listPlan, meta: {} },
114
117
  });
115
- const setup = await testRender(<App home={home} sessionId={s2.id} />, { width: 120, height: 30 });
118
+ const setup = await testRender(<App home={home} sessionId={s2.id} />, {
119
+ width: 120,
120
+ height: 30,
121
+ });
116
122
  await waitForText(setup, "one");
117
123
 
118
124
  // Assert
119
- const lines = setup.captureCharFrame().split("\n").map((line) => line.trimEnd());
125
+ const lines = setup
126
+ .captureCharFrame()
127
+ .split("\n")
128
+ .map((line) => line.trimEnd());
120
129
  const one = lines.findIndex((line) => line.includes("- one"));
121
130
  expect(lines[one + 1]).toContain("- two");
122
131
  });
@@ -1,7 +1,7 @@
1
1
  /** The review panel end to end: the b key cycles expanded -> compact -> hidden -> expanded on the live App, and the collapse mode persists to the user config so it survives a restart. Rendering details are locked by the ReviewPanel stories; this suite proves the App wiring and the persistence. */
2
2
 
3
3
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
4
- import { mkdtempSync, readFileSync, rmSync } from "node:fs";
4
+ import { mkdtempSync, rmSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  import React from "react";
@@ -38,7 +38,10 @@ afterEach(() => {
38
38
  });
39
39
 
40
40
  async function renderApp() {
41
- const setup = await testRender(<App home={home} sessionId={session.id} />, { width: 120, height: 30 });
41
+ const setup = await testRender(<App home={home} sessionId={session.id} />, {
42
+ width: 120,
43
+ height: 30,
44
+ });
42
45
  await waitForText(setup, "Review");
43
46
  return setup;
44
47
  }
@@ -49,7 +52,12 @@ describe("review panel", () => {
49
52
  const setup = await renderApp();
50
53
  // the plan frame widens as the rail shrinks: expanded < compact < hidden.
51
54
  // its width is the column of its top-right corner (the first ╮ on the row).
52
- const planWidth = () => setup.captureCharFrame().split("\n").find((line) => line.includes("╮"))?.indexOf("╮") ?? 0;
55
+ const planWidth = () =>
56
+ setup
57
+ .captureCharFrame()
58
+ .split("\n")
59
+ .find((line) => line.includes("╮"))
60
+ ?.indexOf("╮") ?? 0;
53
61
 
54
62
  // Assert: expanded shows the submit button and leaves room for the rail
55
63
  expect(setup.captureCharFrame()).toContain("Submit review");
@@ -35,7 +35,11 @@ beforeEach(() => {
35
35
  server.start();
36
36
  session = server.core.sessionCreate({
37
37
  workspace: { repoRoot: "/repo", branch: "main" },
38
- artifact: { type: "plan", content: PLAN, meta: { title: "Migration Plan", planPath: "plan.md" } },
38
+ artifact: {
39
+ type: "plan",
40
+ content: PLAN,
41
+ meta: { title: "Migration Plan", planPath: "plan.md" },
42
+ },
39
43
  });
40
44
  });
41
45
  afterEach(() => {
@@ -45,16 +49,17 @@ afterEach(() => {
45
49
  });
46
50
 
47
51
  async function renderApp(options: { readOnly?: boolean } = {}) {
48
- const setup = await testRender(<App home={home} sessionId={session.id} readOnly={options.readOnly ?? false} />, {
49
- width: 120,
50
- height: 32,
51
- });
52
+ const setup = await testRender(
53
+ <App home={home} sessionId={session.id} readOnly={options.readOnly ?? false} />,
54
+ {
55
+ width: 120,
56
+ height: 32,
57
+ },
58
+ );
52
59
  await waitForText(setup, "cueloop");
53
60
  return setup;
54
61
  }
55
62
 
56
- type Setup = Awaited<ReturnType<typeof renderApp>>;
57
-
58
63
  /** Seed annotations directly through the daemon core (all on one block). */
59
64
  function seedAnnotations(count: number): void {
60
65
  const blocks = parseBlocks(PLAN);
package/src/App.test.tsx CHANGED
@@ -9,7 +9,13 @@ import { testRender } from "@opentui/react/test-utils";
9
9
  import { DaemonServer } from "@cueloop/daemon";
10
10
  import type { ReviewSession } from "@cueloop/schema";
11
11
  import { App } from "./App";
12
- import { isolateUserConfig, press, typeText as type, waitForState, waitForText } from "./test-support";
12
+ import {
13
+ isolateUserConfig,
14
+ press,
15
+ typeText as type,
16
+ waitForState,
17
+ waitForText,
18
+ } from "./test-support";
13
19
 
14
20
  const PLAN = `# Migration Plan
15
21
 
@@ -35,7 +41,11 @@ beforeEach(() => {
35
41
  server.start();
36
42
  session = server.core.sessionCreate({
37
43
  workspace: { repoRoot: "/repo", branch: "main" },
38
- artifact: { type: "plan", content: PLAN, meta: { title: "Migration Plan", planPath: "plan.md" } },
44
+ artifact: {
45
+ type: "plan",
46
+ content: PLAN,
47
+ meta: { title: "Migration Plan", planPath: "plan.md" },
48
+ },
39
49
  });
40
50
  });
41
51
  afterEach(() => {
@@ -138,8 +148,8 @@ describe("keyboard grammar", () => {
138
148
  // Act
139
149
  await press(setup, "x");
140
150
 
141
- // Assert
142
- await waitForText(setup, "[cut]");
151
+ // Assert - the rail shows the cut as a removal card titled CUT · me
152
+ await waitForText(setup, "CUT · me");
143
153
  expect(server.core.sessionGet(session.id).workingCopy).not.toContain("move the store");
144
154
 
145
155
  // Act
@@ -221,14 +231,17 @@ describe("submit", () => {
221
231
  });
222
232
 
223
233
  describe("inbox", () => {
224
- test("inbox mode renders and opens a session", async () => {
234
+ test("inbox mode wears the review chrome (header + menu bar) and opens a session", async () => {
225
235
  // Arrange
226
236
  const setup = await testRender(<App home={home} />, { width: 120, height: 32 });
227
- await waitForText(setup, "inbox");
237
+ await waitForText(setup, "cueloop");
228
238
 
229
- // Assert
230
- expect(setup.captureCharFrame()).toContain("inbox (1 pending)");
231
- expect(setup.captureCharFrame()).toContain("Migration Plan");
239
+ // Assert - the same header/menu chrome as review, plus the session row
240
+ const frame = setup.captureCharFrame();
241
+ expect(frame).toContain("cueloop · resume");
242
+ expect(frame).toContain("Migration Plan");
243
+ expect(frame).toContain("menu"); // the shared MenuBar sits at the bottom
244
+ expect(frame).not.toContain("inbox ("); // the old inline header is gone
232
245
 
233
246
  // Act
234
247
  await press(setup, "enter");
@@ -236,4 +249,28 @@ describe("inbox", () => {
236
249
  // Assert
237
250
  await waitForText(setup, "Submit review");
238
251
  });
252
+
253
+ test("the menu opens from the inbox and escape is not a trap", async () => {
254
+ // Arrange
255
+ const setup = await testRender(<App home={home} />, { width: 120, height: 32 });
256
+ await waitForText(setup, "cueloop · resume");
257
+ const lines = setup.captureCharFrame().split("\n");
258
+ const menuRow = lines.findIndex((line) => line.includes("menu"));
259
+ const menuColumn = lines[menuRow]!.indexOf("menu");
260
+
261
+ // Act - open the drop-up from the shared menu bar
262
+ await setup.mockMouse.click(menuColumn + 1, menuRow);
263
+
264
+ // Assert - the drop-up options appear
265
+ await waitForText(setup, "Keybinds");
266
+ expect(setup.captureCharFrame()).toContain("Settings");
267
+
268
+ // Act - escape closes the menu (not a trap), and the inbox nav still works
269
+ await press(setup, "escape");
270
+ await waitForState(setup, () => !setup.captureCharFrame().includes("Keybinds"));
271
+ await press(setup, "enter");
272
+
273
+ // Assert
274
+ await waitForText(setup, "Submit review");
275
+ });
239
276
  });
@@ -1,6 +1,6 @@
1
1
  /** The share toast is non-modal: while it is up, escape still cancels an open overlay. */
2
2
 
3
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
3
+ import { afterEach, beforeEach, describe, mock, test } from "bun:test";
4
4
  import { mkdtempSync, rmSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
@@ -36,7 +36,11 @@ beforeEach(() => {
36
36
  server.start();
37
37
  session = server.core.sessionCreate({
38
38
  workspace: { repoRoot: "/repo", branch: "main" },
39
- artifact: { type: "plan", content: PLAN, meta: { title: "Migration Plan", planPath: "plan.md" } },
39
+ artifact: {
40
+ type: "plan",
41
+ content: PLAN,
42
+ meta: { title: "Migration Plan", planPath: "plan.md" },
43
+ },
40
44
  });
41
45
  });
42
46
  afterEach(() => {
@@ -48,7 +52,10 @@ afterEach(() => {
48
52
  describe("share toast", () => {
49
53
  test("escape cancels an open composer even while the toast is up", async () => {
50
54
  // Arrange
51
- const setup = await testRender(<App home={home} sessionId={session.id} />, { width: 120, height: 32 });
55
+ const setup = await testRender(<App home={home} sessionId={session.id} />, {
56
+ width: 120,
57
+ height: 32,
58
+ });
52
59
  await waitForText(setup, "cueloop");
53
60
 
54
61
  // Act: share raises the toast, then open a composer under it