@cueloop/client 0.1.0-alpha.78 → 0.1.0-alpha.79

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 (92) hide show
  1. package/package.json +14 -13
  2. package/src/App.diff.test.tsx +9 -4
  3. package/src/App.focus.test.ts +1 -59
  4. package/src/App.identity.test.tsx +3 -3
  5. package/src/App.plan-render.test.tsx +209 -0
  6. package/src/App.plan-review.test.tsx +75 -26
  7. package/src/App.readonly.test.tsx +6 -5
  8. package/src/App.share-dialog.test.tsx +172 -0
  9. package/src/App.submit-confirm.test.tsx +6 -4
  10. package/src/App.test.tsx +31 -28
  11. package/src/App.toast.test.tsx +11 -4
  12. package/src/App.tsx +259 -274
  13. package/src/App.walk.test.tsx +9 -8
  14. package/src/App.workbench.test.tsx +86 -0
  15. package/src/annotation-palette.ts +1 -0
  16. package/src/app-screens.tsx +12 -6
  17. package/src/app-view-model.ts +0 -1
  18. package/src/components/ConfirmDialog.stories.tsx +3 -3
  19. package/src/components/DiffContentView.tsx +14 -7
  20. package/src/components/DiscussionMarkerRail.tsx +9 -4
  21. package/src/components/GridTabContent.tsx +8 -2
  22. package/src/components/HandleChipsInput.stories.tsx +24 -0
  23. package/src/components/HandleChipsInput.test.tsx +35 -0
  24. package/src/components/HandleChipsInput.tsx +145 -0
  25. package/src/components/MarkdownGridBlock.stories.tsx +33 -0
  26. package/src/components/MarkdownGridBlock.tsx +72 -0
  27. package/src/components/MarkdownThreadEditor.stories.tsx +15 -0
  28. package/src/components/MarkdownThreadEditor.test.tsx +98 -0
  29. package/src/components/MarkdownThreadEditor.tsx +162 -0
  30. package/src/components/NavModeHint.stories.tsx +40 -0
  31. package/src/components/NavModeHint.tsx +31 -0
  32. package/src/components/SettingsDialog.tsx +1 -0
  33. package/src/components/ShareDialog.stories.tsx +121 -0
  34. package/src/components/ShareDialog.tsx +552 -0
  35. package/src/components/ThreadFooter.tsx +4 -1
  36. package/src/components/ThreadTree.tsx +1 -1
  37. package/src/components/ThreadView.slash.test.tsx +0 -35
  38. package/src/components/ThreadView.tsx +142 -68
  39. package/src/components/Toast.stories.tsx +7 -0
  40. package/src/components/Toast.tsx +15 -3
  41. package/src/components/__snapshots__/stories.test.tsx.snap +258 -134
  42. package/src/components/primitives/Button.tsx +6 -2
  43. package/src/components/primitives/Card.tsx +2 -2
  44. package/src/components/primitives/Dialog.tsx +2 -2
  45. package/src/components/primitives/Switch.stories.tsx +17 -0
  46. package/src/components/primitives/Switch.tsx +32 -0
  47. package/src/components/primitives/Tree.tsx +5 -1
  48. package/src/components/primitives/frame.ts +5 -0
  49. package/src/components/share-dialog-store.ts +58 -0
  50. package/src/components/stories.test.tsx +4 -3
  51. package/src/config.test.ts +18 -0
  52. package/src/config.ts +8 -1
  53. package/src/diff-fold.ts +1 -1
  54. package/src/intent-dispatch.test.ts +12 -3
  55. package/src/intent-dispatch.ts +10 -25
  56. package/src/key-bindings.ts +0 -6
  57. package/src/keymap.ts +0 -1
  58. package/src/markdown-editor-syntax-style.ts +50 -0
  59. package/src/markdown-grid.test.ts +101 -0
  60. package/src/markdown-grid.ts +203 -0
  61. package/src/markdown-highlight.test.ts +61 -0
  62. package/src/markdown-highlight.ts +122 -0
  63. package/src/prototype-browser.test.ts +9 -1
  64. package/src/prototype-browser.ts +22 -0
  65. package/src/run.ts +8 -6
  66. package/src/share.ts +15 -1
  67. package/src/test-support.ts +14 -0
  68. package/src/thread-chords.test.ts +55 -178
  69. package/src/thread-chords.ts +78 -169
  70. package/src/thread-controller.curate.test.ts +1 -0
  71. package/src/thread-controller.live-diff.test.ts +3 -0
  72. package/src/thread-controller.share.test.ts +60 -5
  73. package/src/thread-controller.switch-cache.test.ts +30 -0
  74. package/src/thread-controller.tree.test.ts +6 -1
  75. package/src/thread-controller.ts +229 -54
  76. package/src/use-annotation-surface.tsx +45 -36
  77. package/src/use-settings-dialog.tsx +14 -3
  78. package/src/use-thread-body-editing.tsx +58 -0
  79. package/src/view-plan.test.ts +56 -6
  80. package/src/view-plan.ts +154 -5
  81. package/src/App.share-choice.test.tsx +0 -105
  82. package/src/components/ManageAccessDialog.stories.tsx +0 -36
  83. package/src/components/ManageAccessDialog.test.tsx +0 -94
  84. package/src/components/ManageAccessDialog.tsx +0 -122
  85. package/src/components/ShareChoiceDialog.stories.tsx +0 -34
  86. package/src/components/ShareChoiceDialog.test.tsx +0 -103
  87. package/src/components/ShareChoiceDialog.tsx +0 -80
  88. package/src/components/ThreadsSidebar.stories.tsx +0 -36
  89. package/src/components/ThreadsSidebar.tsx +0 -71
  90. package/src/components/agent-launcher.stories.tsx +0 -26
  91. package/src/components/agent-launcher.test.tsx +0 -50
  92. package/src/components/agent-launcher.tsx +0 -149
@@ -17,16 +17,20 @@ const pushShare = mock(
17
17
  async (
18
18
  _shareId: string,
19
19
  _annotations: Array<Omit<Annotation, "createdAt">>,
20
- _access?: ShareAccess,
20
+ _access?: ShareAccess | "public",
21
21
  ) => {},
22
22
  );
23
23
 
24
+ const revokeShare = mock(async (_shareId: string) => {});
25
+
24
26
  const shareTransport: ShareTransport = {
25
27
  publish: publishShare,
26
28
  pull: pullShare,
27
29
  push: pushShare,
28
30
  watch: () => () => {},
31
+ revoke: revokeShare,
29
32
  parseShareId: (line) => line.match(/^ssh (\S+)@/)?.[1],
33
+ formatShareLine: (id: string) => "ssh " + id + "@cueloop.dev",
30
34
  collaboratorAnnotations: (session) => session.annotations.filter((entry) => entry.author),
31
35
  mergeFromShare,
32
36
  };
@@ -88,6 +92,12 @@ function fakeClient(session: Thread): FakeSessionClient {
88
92
  sessionSetShareId: mock(
89
93
  async (_id: string, shareId: string) => ((session.shareId = shareId), session),
90
94
  ),
95
+ sessionSetShares: mock(
96
+ async (_id: string, shares: import("@cueloop/schema").ShareLink[]) => (
97
+ (session.shares = shares),
98
+ session
99
+ ),
100
+ ),
91
101
  sessionMergeShared: mock(async (_id: string, incoming: { annotations: Annotation[] }) => {
92
102
  const known = new Set(session.annotations.map((existing) => existing.id));
93
103
 
@@ -125,7 +135,7 @@ async function connectedController(
125
135
  }
126
136
 
127
137
  describe("share", () => {
128
- test("stamps the returned share id back on the session", async () => {
138
+ test("publishes a public link and records it on the session", async () => {
129
139
  // Arrange
130
140
  const { controller, client } = await connectedController(sessionFixture());
131
141
 
@@ -133,8 +143,10 @@ describe("share", () => {
133
143
  controller.share();
134
144
  await tick();
135
145
 
136
- // Assert
137
- expect(client.sessionSetShareId).toHaveBeenCalledWith("ses_1", "p_abc123xy");
146
+ // Assert - a public link (no auth) is persisted with the returned id
147
+ expect(client.sessionSetShares).toHaveBeenCalledWith("ses_1", [
148
+ { id: "p_abc123xy", name: undefined, requireAuth: false, allowlist: [], shareBranch: "main" },
149
+ ]);
138
150
  });
139
151
 
140
152
  test("surfaces the ssh line as a toast, not an inline status", async () => {
@@ -148,7 +160,7 @@ describe("share", () => {
148
160
  // Assert
149
161
  expect(controller.getSnapshot().toast).toEqual({
150
162
  body: "ssh p_abc123xy@cueloop.dev",
151
- title: "share link copied",
163
+ title: "link copied",
152
164
  });
153
165
  expect(controller.getSnapshot().status).not.toContain("ssh p_abc123xy@cueloop.dev");
154
166
  });
@@ -504,3 +516,46 @@ describe("startShareSync", () => {
504
516
  expect(streams[1]!.stopped).toBe(false);
505
517
  });
506
518
  });
519
+
520
+ describe("revoke", () => {
521
+ test("unshare revokes the gateway blob for the shared thread", async () => {
522
+ revokeShare.mockClear();
523
+ const { controller } = await connectedController(sessionFixture({ shareId: "p_abc123xy" }));
524
+
525
+ controller.unshare();
526
+ await tick();
527
+
528
+ expect(revokeShare).toHaveBeenCalledWith("p_abc123xy");
529
+ });
530
+
531
+ test("unshare on an unshared thread is a no-op", async () => {
532
+ revokeShare.mockClear();
533
+ const { controller } = await connectedController(sessionFixture());
534
+
535
+ controller.unshare();
536
+ await tick();
537
+
538
+ expect(revokeShare).not.toHaveBeenCalled();
539
+ });
540
+
541
+ test("deleting a shared thread revokes its share first", async () => {
542
+ revokeShare.mockClear();
543
+ const session = sessionFixture({ shareId: "p_abc123xy" });
544
+ const client = fakeClient(session);
545
+
546
+ client.sessionDelete = mock(async () => {});
547
+ const controller = createReviewController({
548
+ sessionId: session.id,
549
+ openClient: async () => client,
550
+ shareTransport,
551
+ });
552
+
553
+ controller.connect();
554
+ await tick();
555
+ controller.deleteSession(session.id);
556
+ await tick();
557
+
558
+ expect(revokeShare).toHaveBeenCalledWith("p_abc123xy");
559
+ expect(client.sessionDelete).toHaveBeenCalledWith(session.id);
560
+ });
561
+ });
@@ -104,6 +104,7 @@ function fakeClient(sessions: Thread[]): SessionClient {
104
104
  sessionSetViewed: unimplemented("sessionSetViewed"),
105
105
  sessionSetTitle: unimplemented("sessionSetTitle"),
106
106
  sessionSetShareId: unimplemented("sessionSetShareId"),
107
+ sessionSetShares: unimplemented("sessionSetShares"),
107
108
  sessionMergeShared: unimplemented("sessionMergeShared"),
108
109
  sessionDelete: unimplemented("sessionDelete"),
109
110
  sessionSetSelfName: unimplemented("sessionSetSelfName"),
@@ -143,4 +144,33 @@ describe("switching threads keeps the cached projection curatable", () => {
143
144
  expect(controller.getSnapshot().status).toContain("change rejected");
144
145
  expect(controller.getSnapshot().session?.workingCopy).toBe("");
145
146
  });
147
+
148
+ test("returning to a diff thread shows the daemon's latest, not the stale sidebar copy", async () => {
149
+ // The sidebar list trails the daemon for a hot-reloading diff (its session.updated
150
+ // refreshes the open thread, not the list). Returning to it must revalidate.
151
+ const staleA = diffSession("ses_a", PATCH_A, FILES_A);
152
+ const freshA = diffSession("ses_a", PATCH_B, FILES_B);
153
+ const other = diffSession("ses_b", PATCH_B, FILES_B);
154
+ const base = fakeClient([staleA, other]);
155
+ const controller = createReviewController({
156
+ sessionId: "ses_a",
157
+ openClient: async () => ({
158
+ ...base,
159
+ // the sidebar keeps the stale copy; the daemon serves the fresh one
160
+ sessionList: async () => [staleA, other],
161
+ sessionGet: async (id: string) => (id === "ses_a" ? freshA : other),
162
+ }),
163
+ });
164
+
165
+ controller.connect();
166
+ await tick();
167
+
168
+ // Visit B, then return to A - open() paints the stale sidebar copy, then revalidates
169
+ controller.open("ses_b");
170
+ await tick();
171
+ controller.open("ses_a");
172
+ await tick();
173
+
174
+ expect(controller.getSnapshot().session?.artifact.content).toBe(PATCH_B);
175
+ });
146
176
  });
@@ -58,7 +58,9 @@ const shareTransport: ShareTransport = {
58
58
  pull: mock(async () => sessionFixture()),
59
59
  push: mock(async () => {}),
60
60
  watch: () => () => {},
61
+ revoke: async () => {},
61
62
  parseShareId: (line) => line.match(/^ssh (\S+)@/)?.[1],
63
+ formatShareLine: (id: string) => "ssh " + id + "@cueloop.dev",
62
64
  collaboratorAnnotations: (session) => session.annotations.filter((entry) => entry.author),
63
65
  mergeFromShare,
64
66
  };
@@ -95,6 +97,7 @@ function fakeClient(session: Thread) {
95
97
  sessionSetViewed: unimplemented("sessionSetViewed"),
96
98
  sessionSetTitle: unimplemented("sessionSetTitle"),
97
99
  sessionSetShareId: mock(async (_id: string, _shareId: string) => session),
100
+ sessionSetShares: mock(async () => session),
98
101
  sessionMergeShared: unimplemented("sessionMergeShared"),
99
102
  sessionDelete: unimplemented("sessionDelete"),
100
103
  sessionSetSelfName: unimplemented("sessionSetSelfName"),
@@ -250,7 +253,9 @@ describe("tree primitives", () => {
250
253
  expect(forked.controller.getSnapshot().session!.id).toBe("ses_1_fork");
251
254
  expect(shared.controller.getSnapshot().session!.id).toBe("ses_1");
252
255
  expect(publish).toHaveBeenCalledWith(expect.objectContaining({ id: "ses_1_fork" }));
253
- expect(shared.client.sessionSetShareId).toHaveBeenCalledWith("ses_1_fork", "p_ses_1_fork");
256
+ expect(shared.client.sessionSetShares).toHaveBeenCalledWith("ses_1_fork", [
257
+ { id: "p_ses_1_fork", requireAuth: false, allowlist: [], shareBranch: "main" },
258
+ ]);
254
259
  expect(shared.controller.getSnapshot().toast?.title).toBe("fork shared - link copied");
255
260
  });
256
261
  });