@cueloop/client 0.1.0-alpha.65 → 0.1.0-alpha.66

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 (78) hide show
  1. package/package.json +5 -5
  2. package/src/App.diff.test.tsx +8 -73
  3. package/src/App.plan-review.test.tsx +4 -185
  4. package/src/App.prototype.test.tsx +1 -8
  5. package/src/App.readonly.test.tsx +6 -28
  6. package/src/App.submit-confirm.test.tsx +19 -72
  7. package/src/App.test.tsx +70 -24
  8. package/src/App.tsx +177 -254
  9. package/src/App.walk.test.tsx +1 -1
  10. package/src/app-screens.tsx +40 -51
  11. package/src/app-view-model.ts +2 -4
  12. package/src/components/AnnotationCard.stories.tsx +1 -1
  13. package/src/components/AppShell.stories.tsx +84 -0
  14. package/src/components/AppShell.tsx +127 -0
  15. package/src/components/Breadcrumb.stories.tsx +1 -1
  16. package/src/components/ChangesColumn.stories.tsx +39 -0
  17. package/src/components/ChangesColumn.tsx +147 -0
  18. package/src/components/CodeBlock.stories.tsx +1 -1
  19. package/src/components/CompletionOverlay.stories.tsx +1 -1
  20. package/src/components/ConfirmCard.stories.tsx +1 -1
  21. package/src/components/ConfirmCard.tsx +3 -4
  22. package/src/components/ConfirmDialog.stories.tsx +1 -1
  23. package/src/components/DiffSheet.stories.tsx +1 -1
  24. package/src/components/InboxList.stories.tsx +23 -8
  25. package/src/components/InboxList.tsx +45 -19
  26. package/src/components/KeybindsDialog.stories.tsx +1 -1
  27. package/src/components/MarkerPopover.stories.tsx +1 -1
  28. package/src/components/MenuBar.stories.tsx +3 -12
  29. package/src/components/MenuBar.tsx +25 -56
  30. package/src/components/PromptDialog.stories.tsx +1 -1
  31. package/src/components/PrototypeSheet.stories.tsx +1 -1
  32. package/src/components/ReviewPanel.stories.tsx +1 -1
  33. package/src/components/ReviewRail.stories.tsx +1 -1
  34. package/src/components/SettingsDialog.stories.tsx +7 -1
  35. package/src/components/SettingsDialog.tsx +112 -66
  36. package/src/components/SettingsRows.stories.tsx +1 -1
  37. package/src/components/ShellHeader.stories.tsx +50 -0
  38. package/src/components/ShellHeader.tsx +137 -0
  39. package/src/components/ThreadFooter.stories.tsx +30 -0
  40. package/src/components/ThreadFooter.tsx +49 -0
  41. package/src/components/ThreadView.slash.test.tsx +113 -0
  42. package/src/components/ThreadView.stories.tsx +1 -1
  43. package/src/components/ThreadView.tsx +99 -11
  44. package/src/components/ThreadsSidebar.stories.tsx +36 -0
  45. package/src/components/ThreadsSidebar.tsx +57 -0
  46. package/src/components/Toast.stories.tsx +1 -1
  47. package/src/components/TreePane.stories.tsx +1 -1
  48. package/src/components/WalkWizard.stories.tsx +1 -1
  49. package/src/components/__snapshots__/stories.test.tsx.snap +348 -138
  50. package/src/components/agent-launcher.stories.tsx +1 -1
  51. package/src/components/file-tree.test.ts +45 -0
  52. package/src/components/file-tree.ts +57 -0
  53. package/src/components/pages/Workspace.stories.tsx +181 -0
  54. package/src/components/primitives/Button.stories.tsx +1 -1
  55. package/src/components/primitives/Card.stories.tsx +1 -1
  56. package/src/components/primitives/Dialog.stories.tsx +1 -1
  57. package/src/components/primitives/IconButton.stories.tsx +36 -0
  58. package/src/components/primitives/IconButton.tsx +43 -0
  59. package/src/components/primitives/StatusBar.stories.tsx +1 -1
  60. package/src/components/primitives/Tabs.stories.tsx +1 -1
  61. package/src/components/primitives/Toolbar.stories.tsx +1 -1
  62. package/src/components/primitives/Tree.stories.tsx +124 -0
  63. package/src/components/primitives/Tree.tsx +87 -0
  64. package/src/components/primitives/icons.ts +38 -0
  65. package/src/components/primitives/tree-model.test.ts +99 -0
  66. package/src/components/primitives/tree-model.ts +137 -0
  67. package/src/components/quick-actions-editor.stories.tsx +1 -1
  68. package/src/components/session-tree.test.ts +114 -0
  69. package/src/components/session-tree.ts +94 -0
  70. package/src/components/stories-app.tsx +142 -82
  71. package/src/components/story-tree.test.ts +100 -0
  72. package/src/components/story.ts +55 -0
  73. package/src/components/theme-context.stories.tsx +1 -1
  74. package/src/session-controller.ts +9 -4
  75. package/src/theme.ts +4 -4
  76. package/src/use-settings-dialog.tsx +9 -0
  77. package/src/App.review-panel.test.tsx +0 -140
  78. package/src/App.tree.test.tsx +0 -224
package/src/App.test.tsx CHANGED
@@ -71,7 +71,7 @@ async function renderApp(sessionId?: string) {
71
71
  }
72
72
 
73
73
  describe("plan rendering", () => {
74
- test("renders the plan with headings, list markers, and the rail", async () => {
74
+ test("renders the plan with headings, list markers, and the footer", async () => {
75
75
  // Arrange
76
76
  const setup = await renderApp();
77
77
 
@@ -82,8 +82,22 @@ describe("plan rendering", () => {
82
82
  expect(frame).toContain("Context");
83
83
  expect(frame).toContain("persists sessions to disk atomically");
84
84
  expect(frame).toContain("· move the store");
85
- expect(frame).toContain("Review");
86
- expect(frame).toContain("Submit review");
85
+ expect(frame).toContain("send message");
86
+ });
87
+
88
+ test("a direct open still populates the Threads sidebar with other pending reviews", async () => {
89
+ // Arrange - a second pending review exists alongside the one opened directly
90
+ server.core.sessionCreate({
91
+ workspace: { repoRoot: "/repo", branch: "main" },
92
+ artifact: { type: "plan", content: "# Other Plan\n", meta: { title: "Other Plan" } },
93
+ });
94
+ const setup = await renderApp();
95
+
96
+ // Act - open the collapsed Threads sidebar (the mirrored panel toggle by the gear)
97
+ await setup.mockMouse.click(3, 1);
98
+
99
+ // Assert - the sidebar lists the other pending review, so you can jump to it
100
+ await waitForText(setup, "Other Plan");
87
101
  });
88
102
  });
89
103
 
@@ -113,7 +127,7 @@ describe("thread view grammar", () => {
113
127
  await pressKey(setup, "RETURN", { meta: true });
114
128
 
115
129
  // Assert
116
- await waitForText(setup, "COMMENT");
130
+ await waitForState(setup, () => server.core.sessionGet(session.id).annotations.length === 1);
117
131
  const stored = server.core.sessionGet(session.id);
118
132
 
119
133
  expect(stored.annotations.length).toBe(1);
@@ -144,9 +158,11 @@ describe("thread view grammar", () => {
144
158
  // Act
145
159
  await pressKey(setup, "x", { meta: true });
146
160
 
147
- // Assert - the rail shows the cut as a removal card titled CUT · me
148
- await waitForText(setup, "CUT · me");
149
- expect(server.core.sessionGet(session.id).workingCopy).not.toContain("move the store");
161
+ // Assert - the cut lands in the working copy
162
+ await waitForState(
163
+ setup,
164
+ () => !(server.core.sessionGet(session.id).workingCopy ?? "").includes("move the store"),
165
+ );
150
166
 
151
167
  // Act
152
168
  await pressKey(setup, "x", { meta: true });
@@ -228,25 +244,24 @@ describe("submit", () => {
228
244
  });
229
245
 
230
246
  describe("inbox", () => {
231
- test("inbox mode wears the review chrome (header + menu bar) and opens a session", async () => {
247
+ test("inbox mode wears the review header and opens a session", async () => {
232
248
  // Arrange
233
249
  const setup = await testRender(<App home={home} />, { width: 120, height: 32 });
234
250
 
235
251
  await waitForText(setup, "cueloop");
236
252
 
237
- // Assert - the same header/menu chrome as review, plus the session row
253
+ // Assert - the same header as review, plus the session row
238
254
  const frame = setup.captureCharFrame();
239
255
 
240
256
  expect(frame).toContain("cueloop · resume");
241
257
  expect(frame).toContain("Migration Plan");
242
- expect(frame).toContain("menu"); // the shared MenuBar sits at the bottom
243
258
  expect(frame).not.toContain("inbox ("); // the old inline header is gone
244
259
 
245
260
  // Act
246
261
  await press(setup, "enter");
247
262
 
248
263
  // Assert
249
- await waitForText(setup, "Submit review");
264
+ await waitForText(setup, "send message");
250
265
  });
251
266
 
252
267
  test("the menu opens from the inbox and escape is not a trap", async () => {
@@ -254,12 +269,9 @@ describe("inbox", () => {
254
269
  const setup = await testRender(<App home={home} />, { width: 120, height: 32 });
255
270
 
256
271
  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
272
 
261
- // Act - open the drop-up from the shared menu bar
262
- await setup.mockMouse.click(menuColumn + 1, menuRow);
273
+ // Act - open the drop-down from the top-left settings gear (header content row)
274
+ await setup.mockMouse.click(1, 1);
263
275
 
264
276
  // Assert - the drop-up options appear
265
277
  await waitForText(setup, "Keybinds");
@@ -271,7 +283,7 @@ describe("inbox", () => {
271
283
  await press(setup, "enter");
272
284
 
273
285
  // Assert
274
- await waitForText(setup, "Submit review");
286
+ await waitForText(setup, "send message");
275
287
  });
276
288
  });
277
289
 
@@ -281,12 +293,9 @@ describe("the thread view and the menu", () => {
281
293
  const setup = await renderApp();
282
294
 
283
295
  await waitForText(setup, "The daemon persists");
284
- const lines = setup.captureCharFrame().split("\n");
285
- const menuRow = lines.findIndex((line) => line.includes("menu"));
286
- const menuColumn = lines[menuRow]!.indexOf("menu");
287
296
 
288
- // Act - open the menu, then the keybinds dialog
289
- await setup.mockMouse.click(menuColumn + 1, menuRow);
297
+ // Act - open the menu from the top-left settings gear, then the keybinds dialog
298
+ await setup.mockMouse.click(1, 1);
290
299
  await waitForText(setup, "Keybinds");
291
300
  const dropUp = setup.captureCharFrame().split("\n");
292
301
  const keybindsRow = dropUp.findIndex((line) => line.includes("Keybinds"));
@@ -298,7 +307,7 @@ describe("the thread view and the menu", () => {
298
307
  const dialog = setup.captureCharFrame();
299
308
 
300
309
  expect(dialog).toContain("⌘⌥m");
301
- expect(dialog).toContain("dismiss an empty draft");
310
+ expect(dialog).toContain("place the caret");
302
311
  expect(dialog).not.toContain("grow/shrink");
303
312
 
304
313
  // Act - a printable behind the dialog must not open a comment; escape closes it
@@ -307,7 +316,44 @@ describe("the thread view and the menu", () => {
307
316
  await press(setup, "escape");
308
317
 
309
318
  // Assert
310
- await waitForState(setup, () => !setup.captureCharFrame().includes("dismiss an empty draft"));
319
+ await waitForState(setup, () => !setup.captureCharFrame().includes("mark text, across blocks"));
311
320
  expect(setup.captureCharFrame()).not.toContain("● x");
312
321
  });
322
+
323
+ test("clicking the Settings nav group is inert and never corrupts the active category", async () => {
324
+ // Arrange - open the settings dialog from the gear
325
+ const setup = await renderApp();
326
+
327
+ await setup.mockMouse.click(1, 1);
328
+ await waitForText(setup, "Keybinds");
329
+
330
+ // the nav folder is the last "Settings" on screen (the first is the dialog title)
331
+ const lines = setup.captureCharFrame().split("\n");
332
+ let folderRow = -1;
333
+ let folderColumn = -1;
334
+
335
+ lines.forEach((line, row) => {
336
+ const column = line.indexOf("Settings");
337
+
338
+ if (column !== -1) {
339
+ folderColumn = column;
340
+ folderRow = row;
341
+ }
342
+ });
343
+
344
+ // Act - click the synthetic Settings group folder, then press a nav key. The
345
+ // key handler dereferences the active category, so a corrupt id would throw
346
+ // (the global key handler swallows the throw into console.error).
347
+ const keyHandlerErrors: string[] = [];
348
+ const originalConsoleError = console.error;
349
+
350
+ console.error = (...args: unknown[]) => keyHandlerErrors.push(args.map(String).join(" "));
351
+ await setup.mockMouse.click(folderColumn + 1, folderRow);
352
+ await pressKey(setup, "ARROW_DOWN");
353
+ console.error = originalConsoleError;
354
+
355
+ // Assert - no key-handler crash, and the dialog is still up
356
+ expect(keyHandlerErrors.filter((line) => line.includes("keypress handler"))).toEqual([]);
357
+ expect(setup.captureCharFrame()).toContain("Keybinds");
358
+ });
313
359
  });