@cueloop/client 0.1.0-alpha.6 → 0.1.0-alpha.61

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 (161) hide show
  1. package/native/build-pty.sh +51 -0
  2. package/native/build.sh +82 -0
  3. package/native/darwin-arm64/libcuelooppty.dylib +0 -0
  4. package/native/darwin-arm64/libcueloopvt.dylib +0 -0
  5. package/native/src/pty.zig +193 -0
  6. package/native/src/shim.zig +114 -0
  7. package/package.json +37 -33
  8. package/src/App.appearance.test.tsx +51 -0
  9. package/src/App.autoclose.test.tsx +73 -47
  10. package/src/App.diff.test.tsx +169 -22
  11. package/src/App.export.test.tsx +49 -37
  12. package/src/App.plan-review.test.tsx +714 -0
  13. package/src/App.prototype.test.tsx +194 -0
  14. package/src/App.readonly.test.tsx +69 -35
  15. package/src/App.rendering.test.tsx +59 -31
  16. package/src/App.review-panel.test.tsx +143 -0
  17. package/src/App.submit-confirm.test.tsx +229 -0
  18. package/src/App.test.tsx +139 -68
  19. package/src/App.toast.test.tsx +77 -0
  20. package/src/App.tsx +0 -0
  21. package/src/App.walk.test.tsx +299 -0
  22. package/src/__snapshots__/view-plan.regression.test.ts.snap +100 -0
  23. package/src/app-key-state.ts +61 -0
  24. package/src/app-screens.tsx +270 -0
  25. package/src/app-view-model.ts +307 -0
  26. package/src/attribution.test.ts +31 -0
  27. package/src/attribution.ts +42 -0
  28. package/src/clipboard.test.ts +23 -0
  29. package/src/clipboard.ts +33 -0
  30. package/src/components/AnnotationCard.compose-rows.test.ts +49 -0
  31. package/src/components/AnnotationCard.stories.tsx +94 -0
  32. package/src/components/AnnotationCard.tsx +250 -0
  33. package/src/components/Breadcrumb.stories.tsx +19 -0
  34. package/src/components/Breadcrumb.tsx +45 -0
  35. package/src/components/CodeBlock.stories.tsx +42 -0
  36. package/src/components/CodeBlock.tsx +84 -0
  37. package/src/components/CompletionOverlay.stories.tsx +23 -0
  38. package/src/components/CompletionOverlay.tsx +60 -0
  39. package/src/components/ConfirmCard.stories.tsx +35 -0
  40. package/src/components/ConfirmCard.tsx +129 -0
  41. package/src/components/ConfirmDialog.stories.tsx +21 -0
  42. package/src/components/ConfirmDialog.tsx +66 -0
  43. package/src/components/DiffSheet.scroll.test.tsx +135 -0
  44. package/src/components/DiffSheet.stories.tsx +78 -0
  45. package/src/components/DiffSheet.syntax.test.tsx +65 -0
  46. package/src/components/DiffSheet.tsx +374 -0
  47. package/src/components/InboxList.stories.tsx +31 -0
  48. package/src/components/InboxList.tsx +64 -0
  49. package/src/components/KeybindsDialog.stories.tsx +31 -0
  50. package/src/components/KeybindsDialog.tsx +59 -0
  51. package/src/components/MarkerPopover.stories.tsx +58 -0
  52. package/src/components/MarkerPopover.tsx +109 -0
  53. package/src/components/MenuBar.stories.tsx +37 -0
  54. package/src/components/MenuBar.tsx +75 -0
  55. package/src/components/PlanSheet.stories.tsx +113 -0
  56. package/src/components/PlanSheet.tsx +422 -0
  57. package/src/components/PromptDialog.stories.tsx +36 -0
  58. package/src/components/PromptDialog.tsx +80 -0
  59. package/src/components/PrototypeSheet.stories.tsx +20 -0
  60. package/src/components/PrototypeSheet.tsx +450 -0
  61. package/src/components/ReviewPanel.stories.tsx +86 -0
  62. package/src/components/ReviewPanel.tsx +103 -0
  63. package/src/components/ReviewRail.stories.tsx +218 -0
  64. package/src/components/ReviewRail.tsx +318 -0
  65. package/src/components/SettingsDialog.stories.tsx +111 -0
  66. package/src/components/SettingsDialog.tsx +147 -0
  67. package/src/components/SettingsRows.stories.tsx +18 -0
  68. package/src/components/SettingsRows.tsx +112 -0
  69. package/src/components/Toast.stories.tsx +12 -0
  70. package/src/components/Toast.tsx +45 -0
  71. package/src/components/WalkWizard.stories.tsx +100 -0
  72. package/src/components/WalkWizard.tsx +152 -0
  73. package/src/components/__snapshots__/stories.test.tsx.snap +1857 -0
  74. package/src/components/agent-launcher.stories.tsx +26 -0
  75. package/src/components/agent-launcher.test.tsx +50 -0
  76. package/src/components/agent-launcher.tsx +149 -0
  77. package/src/components/diff-sheet-layout.test.ts +128 -0
  78. package/src/components/diff-sheet-layout.ts +184 -0
  79. package/src/components/plan-sheet-run-style.ts +90 -0
  80. package/src/components/primitives/Button.stories.tsx +32 -0
  81. package/src/components/primitives/Button.tsx +52 -0
  82. package/src/components/primitives/Card.stories.tsx +25 -0
  83. package/src/components/primitives/Card.tsx +60 -0
  84. package/src/components/primitives/Dialog.stories.tsx +24 -0
  85. package/src/components/primitives/Dialog.tsx +66 -0
  86. package/src/components/primitives/StatusBar.stories.tsx +11 -0
  87. package/src/components/primitives/StatusBar.tsx +23 -0
  88. package/src/components/primitives/Tabs.stories.tsx +29 -0
  89. package/src/components/primitives/Tabs.tsx +102 -0
  90. package/src/components/primitives/Toolbar.stories.tsx +17 -0
  91. package/src/components/primitives/Toolbar.tsx +14 -0
  92. package/src/components/primitives/frame.test.ts +12 -0
  93. package/src/components/primitives/frame.ts +14 -0
  94. package/src/components/quick-actions-editor.stories.tsx +54 -0
  95. package/src/components/quick-actions-editor.tsx +138 -0
  96. package/src/components/stories-app.tsx +124 -0
  97. package/src/components/stories.test.tsx +66 -0
  98. package/src/components/story-fixtures.ts +98 -0
  99. package/src/components/story.ts +75 -0
  100. package/src/components/syntax-highlight.ts +102 -0
  101. package/src/components/terminal-pane.test.tsx +40 -0
  102. package/src/components/terminal-pane.ts +193 -0
  103. package/src/components/theme-context.stories.tsx +46 -0
  104. package/src/components/theme-context.tsx +37 -0
  105. package/src/components/truncate-text.ts +7 -0
  106. package/src/config.test.ts +448 -31
  107. package/src/config.ts +339 -57
  108. package/src/diff-hunk-curate.test.ts +315 -0
  109. package/src/diff-hunk-curate.ts +253 -0
  110. package/src/diff-intraline.test.ts +165 -0
  111. package/src/diff-intraline.ts +146 -0
  112. package/src/diff-syntax.test.ts +96 -0
  113. package/src/diff-syntax.ts +176 -0
  114. package/src/editor.test.ts +149 -0
  115. package/src/editor.ts +135 -9
  116. package/src/ghostty-terminal.test.ts +84 -0
  117. package/src/ghostty-terminal.ts +176 -0
  118. package/src/herdr.test.ts +9 -34
  119. package/src/herdr.ts +17 -21
  120. package/src/index.ts +10 -0
  121. package/src/integrations.test.ts +41 -0
  122. package/src/integrations.ts +32 -0
  123. package/src/intent-dispatch.test.ts +499 -0
  124. package/src/intent-dispatch.ts +585 -0
  125. package/src/key-bindings.test.ts +156 -0
  126. package/src/key-bindings.ts +452 -0
  127. package/src/keymap.test.ts +635 -0
  128. package/src/keymap.ts +404 -0
  129. package/src/kitty-image.test.ts +66 -0
  130. package/src/kitty-image.ts +138 -0
  131. package/src/prototype-browser.test.ts +56 -0
  132. package/src/prototype-browser.ts +289 -0
  133. package/src/pty.test.ts +81 -0
  134. package/src/pty.ts +237 -0
  135. package/src/review-panel.test.ts +113 -0
  136. package/src/review-panel.ts +83 -0
  137. package/src/run.ts +16 -5
  138. package/src/serve.test.ts +25 -9
  139. package/src/serve.ts +19 -8
  140. package/src/session-controller.curate.test.ts +260 -0
  141. package/src/session-controller.share.test.ts +320 -0
  142. package/src/session-controller.ts +993 -0
  143. package/src/share.ts +120 -0
  144. package/src/test-support.test.ts +60 -0
  145. package/src/test-support.ts +197 -0
  146. package/src/theme-presets.test.ts +51 -0
  147. package/src/theme-presets.ts +187 -0
  148. package/src/theme.ts +64 -17
  149. package/src/use-settings-dialog.tsx +275 -0
  150. package/src/version.ts +5 -0
  151. package/src/view-diff.test.ts +22 -8
  152. package/src/view-diff.ts +39 -23
  153. package/src/view-plan.regression.test.ts +60 -0
  154. package/src/view-plan.test.ts +406 -0
  155. package/src/view-plan.ts +554 -0
  156. package/src/walk.test.ts +121 -0
  157. package/src/walk.ts +74 -0
  158. package/src/syntax.test.ts +0 -41
  159. package/src/syntax.ts +0 -88
  160. package/src/view.test.ts +0 -157
  161. package/src/view.ts +0 -0
@@ -0,0 +1,299 @@
1
+ /** The guided walk in the virtual terminal: the wizard steps every changed file with a plain progress title, ] marks viewed and persists with the session, esc keeps progress, w resumes at the first unviewed file (across App instances via the daemon round-trip), the agent-note block renders only for files carrying a note, and the end card hands over to the submit confirm with the honest viewed count. */
2
+
3
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
4
+ import { mkdtempSync, rmSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import React from "react";
8
+ import { testRender } from "@opentui/react/test-utils";
9
+ import { DaemonServer } from "@cueloop/daemon";
10
+ import type { ReviewSession } from "@cueloop/schema";
11
+ import { App } from "./App";
12
+ import { DARK } from "./theme";
13
+ import {
14
+ isolateUserConfig,
15
+ press,
16
+ waitForState,
17
+ waitForText,
18
+ waitForTextGone,
19
+ } from "./test-support";
20
+
21
+ const PATCH = `diff --git a/src/a.ts b/src/a.ts
22
+ index 111..222 100644
23
+ --- a/src/a.ts
24
+ +++ b/src/a.ts
25
+ @@ -1,2 +1,2 @@
26
+ export const a = 1;
27
+ -export const b = 2;
28
+ +export const b = 3;
29
+ diff --git a/src/b.ts b/src/b.ts
30
+ index 333..444 100644
31
+ --- a/src/b.ts
32
+ +++ b/src/b.ts
33
+ @@ -1,2 +1,2 @@
34
+ // header
35
+ -old line
36
+ +new line
37
+ diff --git a/src/c.ts b/src/c.ts
38
+ index 555..666 100644
39
+ --- a/src/c.ts
40
+ +++ b/src/c.ts
41
+ @@ -1,1 +1,2 @@
42
+ keep
43
+ +added tail
44
+ `;
45
+
46
+ let home: string;
47
+ let restoreUserConfig: () => void;
48
+ let server: DaemonServer;
49
+ let session: ReviewSession;
50
+
51
+ beforeEach(() => {
52
+ home = mkdtempSync(join(tmpdir(), "cueloop-walk-"));
53
+ restoreUserConfig = isolateUserConfig(home);
54
+ server = new DaemonServer({ home, idleExitMs: 0 });
55
+ server.start();
56
+ session = server.core.sessionCreate({
57
+ workspace: { repoRoot: "/repo", branch: "main" },
58
+ artifact: { type: "diff", content: PATCH, meta: { title: "working tree" } },
59
+ });
60
+ });
61
+ afterEach(() => {
62
+ restoreUserConfig();
63
+ server.stop();
64
+ rmSync(home, { recursive: true, force: true });
65
+ });
66
+
67
+ async function renderApp() {
68
+ const setup = await testRender(<App home={home} sessionId={session.id} />, {
69
+ width: 120,
70
+ height: 34,
71
+ });
72
+
73
+ // Wait for session content, not just the chrome: pressing w before the diff
74
+ // loads drops the walk intent (no session, no diff view) and hangs the test.
75
+ await waitForText(setup, "src/a.ts");
76
+
77
+ return setup;
78
+ }
79
+
80
+ type Setup = Awaited<ReturnType<typeof renderApp>>;
81
+
82
+ /** Foreground colors (hex) of every styled span containing the needle. */
83
+ function foregroundsOf(setup: Setup, needle: string): string[] {
84
+ const foregrounds: string[] = [];
85
+
86
+ for (const line of setup.captureSpans().lines) {
87
+ for (const span of line.spans) {
88
+ if (!span.text.includes(needle)) continue;
89
+ const [red, green, blue] = span.fg.toInts();
90
+
91
+ foregrounds.push(
92
+ "#" + [red, green, blue].map((part) => part.toString(16).padStart(2, "0")).join(""),
93
+ );
94
+ }
95
+ }
96
+
97
+ return foregrounds;
98
+ }
99
+
100
+ describe("the guided walk", () => {
101
+ test("w walks all three files: progress title, persisted marks, end card, submit hand-over", async () => {
102
+ // Arrange
103
+ const setup = await renderApp();
104
+
105
+ // Act
106
+ await press(setup, "w");
107
+
108
+ // Assert
109
+ await waitForText(setup, "file 1 of 3 · 0 viewed");
110
+ expect(setup.captureCharFrame()).toContain("src/a.ts");
111
+ expect(setup.captureCharFrame()).toContain("+1 -1");
112
+
113
+ // Act
114
+ await press(setup, "]");
115
+
116
+ // Assert
117
+ await waitForText(setup, "file 2 of 3 · 1 viewed");
118
+ // the viewed mark rides the session record, not the client
119
+ await waitForState(setup, () =>
120
+ (server.core.sessionGet(session.id).viewedPaths ?? []).includes("src/a.ts"),
121
+ );
122
+
123
+ // Act
124
+ // [ steps back; the revisited card shows its viewed marker
125
+ await press(setup, "[");
126
+
127
+ // Assert
128
+ await waitForText(setup, "file 1 of 3 · 1 viewed");
129
+ expect(setup.captureCharFrame()).toContain("· viewed");
130
+
131
+ // Act
132
+ await press(setup, "]");
133
+
134
+ // Assert
135
+ await waitForText(setup, "file 2 of 3 · 1 viewed");
136
+
137
+ // Act
138
+ await press(setup, "]");
139
+
140
+ // Assert
141
+ await waitForText(setup, "file 3 of 3 · 2 viewed");
142
+
143
+ // Act
144
+ await press(setup, "]");
145
+
146
+ // Assert
147
+ await waitForText(setup, "walk complete");
148
+ expect(setup.captureCharFrame()).toContain("every file viewed (3/3)");
149
+ expect(server.core.sessionGet(session.id).viewedPaths).toEqual([
150
+ "src/a.ts",
151
+ "src/b.ts",
152
+ "src/c.ts",
153
+ ]);
154
+
155
+ // Act
156
+ // the end card's return leaves the walk and opens the rail confirm
157
+ await press(setup, "enter");
158
+
159
+ // Assert
160
+ await waitForText(setup, "submit review");
161
+ expect(setup.captureCharFrame()).toContain("3/3 files viewed");
162
+
163
+ // Act
164
+ await press(setup, "enter");
165
+
166
+ // Assert
167
+ await waitForState(setup, () => server.core.sessionGet(session.id).status === "resolved");
168
+ });
169
+
170
+ test("esc keeps progress and w resumes at the first unviewed file", async () => {
171
+ // Arrange
172
+ const setup = await renderApp();
173
+
174
+ await press(setup, "w");
175
+ await waitForText(setup, "file 1 of 3 · 0 viewed");
176
+ await press(setup, "]");
177
+ await waitForText(setup, "file 2 of 3 · 1 viewed");
178
+
179
+ // Act
180
+ await press(setup, "escape");
181
+
182
+ // Assert
183
+ await waitForTextGone(setup, "file 2 of 3");
184
+ // the sheet is back at full strength: no wizard card on screen
185
+ expect(setup.captureCharFrame()).not.toContain("· 1 viewed");
186
+
187
+ // Act
188
+ await press(setup, "w");
189
+
190
+ // Assert
191
+ await waitForText(setup, "file 2 of 3 · 1 viewed");
192
+ });
193
+
194
+ test("a half-walked review resumes across App instances via the daemon", async () => {
195
+ // Arrange
196
+ const first = await renderApp();
197
+
198
+ await press(first, "w");
199
+ await waitForText(first, "file 1 of 3 · 0 viewed");
200
+ await press(first, "]");
201
+ await press(first, "]");
202
+ await waitForText(first, "file 3 of 3 · 2 viewed");
203
+ await waitForState(
204
+ first,
205
+ () => (server.core.sessionGet(session.id).viewedPaths ?? []).length === 2,
206
+ );
207
+ first.renderer.destroy();
208
+
209
+ // Act
210
+ // a fresh client reads the viewed set back from the session record
211
+ const second = await renderApp();
212
+
213
+ await press(second, "w");
214
+
215
+ // Assert
216
+ await waitForText(second, "file 3 of 3 · 2 viewed");
217
+ expect(second.captureCharFrame()).toContain("src/c.ts");
218
+ // two full App boots with daemon round-trips need more than the default
219
+ // budget, and the whole-suite publish lane has timed even 15s out; give the
220
+ // double-boot chain generous headroom so runner load cannot flake it.
221
+ }, 60_000);
222
+
223
+ test("the agent-note block renders only for files carrying a note", async () => {
224
+ // Arrange
225
+ server.core.sessionAnnotate(session.id, {
226
+ id: "note-b",
227
+ kind: "note",
228
+ anchor: { quote: "src/b.ts", prefix: "", suffix: "" },
229
+ body: "Swaps the stale line for the new constant.",
230
+ });
231
+ const setup = await renderApp();
232
+
233
+ // Act
234
+ await press(setup, "w");
235
+
236
+ // Assert
237
+ await waitForText(setup, "file 1 of 3");
238
+ expect(setup.captureCharFrame()).not.toContain("agent note");
239
+
240
+ // Act
241
+ await press(setup, "]");
242
+
243
+ // Assert
244
+ await waitForText(setup, "agent note");
245
+ expect(setup.captureCharFrame()).toContain("Swaps the stale line for the new constant.");
246
+
247
+ // Act
248
+ // a note is agent context, not reviewer feedback: it neither counts nor
249
+ // flips the default verdict, and never comes back in the feedback doc
250
+ await press(setup, "escape");
251
+ await waitForTextGone(setup, "agent note");
252
+ await press(setup, "enter");
253
+ // nothing pending despite the note, so the confirm card defaults to approve
254
+ await waitForText(setup, "[Approve]");
255
+ await press(setup, "enter");
256
+
257
+ // Assert
258
+ await waitForState(setup, () => server.core.sessionGet(session.id).status === "resolved");
259
+ const resolved = server.core.sessionGet(session.id);
260
+
261
+ expect(resolved.verdict!.kind).toBe("approve");
262
+ expect(resolved.verdict!.feedback).not.toContain("Swaps the stale line");
263
+ });
264
+
265
+ test("the sheet dims behind the wizard; the preview keeps the diff colors", async () => {
266
+ // Arrange
267
+ const setup = await renderApp();
268
+
269
+ // Assert
270
+ // before the walk the changed word on the added line wears the insertion color
271
+ await waitForText(setup, "+new line");
272
+ expect(foregroundsOf(setup, "new")).toContain(DARK.insertedForeground);
273
+
274
+ // Act
275
+ await press(setup, "w");
276
+
277
+ // Assert
278
+ await waitForText(setup, "file 1 of 3 · 0 viewed");
279
+ // dimmed: the sheet's changed word drops to the dim token...
280
+ expect(foregroundsOf(setup, "new")).toEqual([DARK.textDim]);
281
+ // ...while the wizard preview keeps the insertion color
282
+ expect(foregroundsOf(setup, "+export const b = 3;")).toContain(DARK.insertedForeground);
283
+ });
284
+
285
+ test("walking a resolved review answers read-only", async () => {
286
+ // Arrange
287
+ server.core.sessionResolve(session.id, "approve", "");
288
+ const setup = await renderApp();
289
+
290
+ await waitForText(setup, "resolved");
291
+
292
+ // Act
293
+ await press(setup, "w");
294
+
295
+ // Assert
296
+ await waitForText(setup, "review submitted - read-only");
297
+ expect(setup.captureCharFrame()).not.toContain("file 1 of 3");
298
+ });
299
+ });
@@ -0,0 +1,100 @@
1
+ // Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2
+
3
+ exports[`view-plan regression harness (M0) blockRuns output is stable across the representative blocks 1`] = `
4
+ [
5
+ {
6
+ "runs": [
7
+ {
8
+ "role": "plain",
9
+ "start": 0,
10
+ "text": "Heading with ",
11
+ },
12
+ {
13
+ "role": "strong",
14
+ "start": 15,
15
+ "text": "bold",
16
+ },
17
+ ],
18
+ "text": "Heading with **bold**",
19
+ "type": "same",
20
+ },
21
+ {
22
+ "runs": [
23
+ {
24
+ "role": "plain",
25
+ "start": 0,
26
+ "text": "A paragraph with ",
27
+ },
28
+ {
29
+ "role": "strong",
30
+ "start": 19,
31
+ "text": "strong",
32
+ },
33
+ {
34
+ "role": "plain",
35
+ "start": 27,
36
+ "text": ", ",
37
+ },
38
+ {
39
+ "role": "em",
40
+ "start": 30,
41
+ "text": "em",
42
+ },
43
+ {
44
+ "role": "plain",
45
+ "start": 33,
46
+ "text": ", ",
47
+ },
48
+ {
49
+ "role": "code",
50
+ "start": 36,
51
+ "text": "code",
52
+ },
53
+ {
54
+ "role": "plain",
55
+ "start": 41,
56
+ "text": ", ",
57
+ },
58
+ {
59
+ "role": "strike",
60
+ "start": 45,
61
+ "text": "strike",
62
+ },
63
+ {
64
+ "role": "plain",
65
+ "start": 53,
66
+ "text": " and a ",
67
+ },
68
+ {
69
+ "href": "https://example.com",
70
+ "role": "link",
71
+ "start": 61,
72
+ "text": "label",
73
+ },
74
+ {
75
+ "role": "plain",
76
+ "start": 88,
77
+ "text": ".",
78
+ },
79
+ ],
80
+ "text": "A paragraph with **strong**, *em*, \`code\`, ~~strike~~ and a [label](https://example.com).",
81
+ "type": "same",
82
+ },
83
+ {
84
+ "runs": [
85
+ {
86
+ "role": "plain",
87
+ "start": 0,
88
+ "text": "a list item with ",
89
+ },
90
+ {
91
+ "role": "em",
92
+ "start": 18,
93
+ "text": "emphasis",
94
+ },
95
+ ],
96
+ "text": "a list item with *emphasis*",
97
+ "type": "same",
98
+ },
99
+ ]
100
+ `;
@@ -0,0 +1,61 @@
1
+ import type { ReviewSession } from "@cueloop/schema";
2
+ import type { Mode } from "./intent-dispatch";
3
+ import type { KeyState } from "./keymap";
4
+ import type { DisplayBlock } from "./view-plan";
5
+ import type { DiffRow } from "./view-diff";
6
+ import type { WalkFile } from "./walk";
7
+
8
+ export function buildKeyState(params: {
9
+ keys: Record<string, string[]>;
10
+ observer: boolean;
11
+ isOwner: boolean;
12
+ overlay: KeyState["overlay"];
13
+ session: ReviewSession | null;
14
+ isDiff: boolean;
15
+ mode: Mode;
16
+ resolved: boolean;
17
+ inbox: ReviewSession[] | null;
18
+ focusedAnnotationId: string | undefined;
19
+ walk: { index: number } | null;
20
+ walkFileList: WalkFile[];
21
+ rows: DiffRow[];
22
+ cursor: number;
23
+ display: DisplayBlock[];
24
+ }): KeyState {
25
+ const {
26
+ keys,
27
+ observer,
28
+ isOwner,
29
+ overlay,
30
+ session,
31
+ isDiff,
32
+ mode,
33
+ resolved,
34
+ inbox,
35
+ focusedAnnotationId,
36
+ walk,
37
+ walkFileList,
38
+ rows,
39
+ cursor,
40
+ display,
41
+ } = params;
42
+
43
+ return {
44
+ keys,
45
+ readOnly: observer,
46
+ canEditPlan: isOwner,
47
+ canSubmitVerdict: isOwner,
48
+ canShare: isOwner,
49
+ overlay,
50
+ view: !session ? "inbox" : isDiff ? "diff" : "plan",
51
+ spanMode: mode.type === "span",
52
+ resolved,
53
+ hasInboxItems: !!inbox?.length,
54
+ annotationCount: session?.annotations.length ?? 0,
55
+ hasFocusedAnnotation: focusedAnnotationId !== undefined,
56
+ walkAtEnd: walk !== null && walk.index >= walkFileList.length,
57
+ cursorAnnotatable: isDiff
58
+ ? rows[cursor] !== undefined && rows[cursor]!.kind !== "file" && rows[cursor]!.kind !== "hunk"
59
+ : !!display[cursor]?.work,
60
+ };
61
+ }