@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.
- package/package.json +27 -26
- package/src/App.autoclose.test.tsx +7 -4
- package/src/App.diff.test.tsx +130 -4
- package/src/App.export.test.tsx +22 -4
- package/src/App.plan-review.test.tsx +127 -27
- package/src/App.readonly.test.tsx +31 -8
- package/src/App.rendering.test.tsx +12 -3
- package/src/App.review-panel.test.tsx +11 -3
- package/src/App.submit-confirm.test.tsx +12 -7
- package/src/App.test.tsx +46 -9
- package/src/App.toast.test.tsx +10 -3
- package/src/App.tsx +400 -100
- package/src/App.walk.test.tsx +30 -10
- package/src/attribution.ts +8 -2
- package/src/clipboard.ts +5 -1
- package/src/components/AnnotationCard.stories.tsx +28 -9
- package/src/components/AnnotationCard.tsx +53 -38
- package/src/components/Breadcrumb.tsx +7 -1
- package/src/components/CodeBlock.stories.tsx +19 -2
- package/src/components/CodeBlock.tsx +24 -9
- package/src/components/CompletionOverlay.tsx +13 -3
- package/src/components/ConfirmCard.stories.tsx +22 -3
- package/src/components/ConfirmCard.tsx +22 -5
- package/src/components/ConfirmDialog.tsx +9 -1
- package/src/components/DiffSheet.scroll.test.tsx +123 -0
- package/src/components/DiffSheet.stories.tsx +18 -2
- package/src/components/DiffSheet.syntax.test.tsx +62 -0
- package/src/components/DiffSheet.tsx +184 -98
- package/src/components/InboxList.stories.tsx +16 -3
- package/src/components/InboxList.tsx +17 -10
- package/src/components/KeybindsDialog.tsx +19 -3
- package/src/components/MarkerPopover.stories.tsx +58 -0
- package/src/components/MarkerPopover.tsx +102 -0
- package/src/components/MenuBar.stories.tsx +5 -1
- package/src/components/MenuBar.tsx +18 -2
- package/src/components/PlanSheet.stories.tsx +34 -2
- package/src/components/PlanSheet.tsx +141 -32
- package/src/components/PromptDialog.tsx +18 -2
- package/src/components/ReviewPanel.stories.tsx +13 -3
- package/src/components/ReviewPanel.tsx +8 -4
- package/src/components/ReviewRail.stories.tsx +145 -72
- package/src/components/ReviewRail.tsx +177 -38
- package/src/components/SettingsDialog.stories.tsx +51 -3
- package/src/components/SettingsDialog.tsx +8 -3
- package/src/components/SettingsRows.tsx +19 -3
- package/src/components/Toast.tsx +17 -2
- package/src/components/WalkWizard.stories.tsx +13 -2
- package/src/components/WalkWizard.tsx +9 -3
- package/src/components/__snapshots__/stories.test.tsx.snap +281 -83
- package/src/components/diff-sheet-layout.test.ts +127 -0
- package/src/components/diff-sheet-layout.ts +167 -0
- package/src/components/primitives/Button.tsx +5 -1
- package/src/components/primitives/Dialog.tsx +9 -1
- package/src/components/primitives/Tabs.tsx +24 -4
- package/src/components/stories-app.tsx +34 -6
- package/src/components/story-fixtures.ts +11 -3
- package/src/components/story.ts +3 -1
- package/src/components/syntax-highlight.ts +47 -12
- package/src/components/theme-context.stories.tsx +6 -1
- package/src/components/theme-context.tsx +7 -1
- package/src/config.test.ts +178 -2
- package/src/config.ts +121 -23
- package/src/diff-hunk-curate.test.ts +315 -0
- package/src/diff-hunk-curate.ts +233 -0
- package/src/diff-intraline.test.ts +164 -0
- package/src/diff-intraline.ts +132 -0
- package/src/diff-syntax.test.ts +96 -0
- package/src/diff-syntax.ts +154 -0
- package/src/editor.test.ts +13 -4
- package/src/editor.ts +31 -5
- package/src/herdr.ts +8 -2
- package/src/index.ts +9 -1
- package/src/integrations.ts +3 -1
- package/src/intent-dispatch.test.ts +193 -9
- package/src/intent-dispatch.ts +151 -18
- package/src/key-bindings.test.ts +19 -9
- package/src/key-bindings.ts +36 -14
- package/src/keymap.test.ts +171 -26
- package/src/keymap.ts +83 -16
- package/src/review-panel.test.ts +3 -1
- package/src/review-panel.ts +9 -2
- package/src/serve.test.ts +11 -3
- package/src/session-controller.curate.test.ts +253 -0
- package/src/session-controller.share.test.ts +65 -17
- package/src/session-controller.ts +301 -26
- package/src/share.ts +41 -8
- package/src/test-support.ts +16 -6
- package/src/theme-presets.ts +168 -0
- package/src/theme.ts +1 -3
- package/src/view-diff.ts +15 -4
- package/src/view-plan.test.ts +40 -6
- package/src/view-plan.ts +71 -28
- package/src/walk.test.ts +7 -2
- package/src/walk.ts +7 -2
package/src/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export { runClient, type RunClientOptions } from "./run";
|
|
2
2
|
export { serveClient, type ServeHandle, type ServeOptions } from "./serve";
|
|
3
3
|
export { App, type AppProps } from "./App";
|
|
4
|
-
export {
|
|
4
|
+
export {
|
|
5
|
+
collaboratorAnnotations,
|
|
6
|
+
publishShare,
|
|
7
|
+
pullShare,
|
|
8
|
+
pushShare,
|
|
9
|
+
shareIdFromLine,
|
|
10
|
+
type ShareResult,
|
|
11
|
+
type ShareTarget,
|
|
12
|
+
} from "./share";
|
package/src/integrations.ts
CHANGED
|
@@ -18,7 +18,9 @@ export interface BundledExporter {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/** Load the bundled integrations from config as generic exporters. */
|
|
21
|
-
export async function loadBundledExporters(
|
|
21
|
+
export async function loadBundledExporters(
|
|
22
|
+
integrations: IntegrationsConfig,
|
|
23
|
+
): Promise<BundledExporter[]> {
|
|
22
24
|
const registry = new Registry();
|
|
23
25
|
const obsidian = integrations.obsidian;
|
|
24
26
|
const record = await registry.load("obsidian", createObsidianExtension(obsidian));
|
|
@@ -21,14 +21,16 @@ function sessionWith(annotationIds: string[]): ReviewSession {
|
|
|
21
21
|
} as unknown as ReviewSession;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/** A
|
|
25
|
-
function
|
|
26
|
-
|
|
24
|
+
/** A controller where every verb is a mock; annotate returns undefined by default. */
|
|
25
|
+
function baseController() {
|
|
26
|
+
return {
|
|
27
27
|
setStatus: mock(),
|
|
28
28
|
open: mock(),
|
|
29
29
|
deleteSession: mock(),
|
|
30
30
|
setSelfName: mock(),
|
|
31
31
|
cut: mock(),
|
|
32
|
+
curationItems: mock(() => []),
|
|
33
|
+
restoreCuration: mock(),
|
|
32
34
|
annotate: mock(),
|
|
33
35
|
updateAnnotation: mock(),
|
|
34
36
|
removeAnnotation: mock(),
|
|
@@ -43,6 +45,11 @@ function makeDeps(overrides: Partial<IntentDispatchDeps> = {}): IntentDispatchDe
|
|
|
43
45
|
dismissCompletion: mock(),
|
|
44
46
|
saveReviewPanel: mock(),
|
|
45
47
|
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A deps bag where every effect is a mock and every read has a plain default. */
|
|
51
|
+
function makeDeps(overrides: Partial<IntentDispatchDeps> = {}): IntentDispatchDeps {
|
|
52
|
+
const controller = baseController();
|
|
46
53
|
return {
|
|
47
54
|
controller: controller as unknown as IntentDispatchDeps["controller"],
|
|
48
55
|
onExit: mock(),
|
|
@@ -58,7 +65,9 @@ function makeDeps(overrides: Partial<IntentDispatchDeps> = {}): IntentDispatchDe
|
|
|
58
65
|
reviewWidth: 34,
|
|
59
66
|
terminalWidth: 120,
|
|
60
67
|
focusedAnnotationId: undefined,
|
|
68
|
+
selectedCurationId: undefined,
|
|
61
69
|
authorNames: {},
|
|
70
|
+
quickActions: [],
|
|
62
71
|
renameAuthor: mock(),
|
|
63
72
|
liveInput: { current: "" },
|
|
64
73
|
reviewWidthRef: { current: 34 },
|
|
@@ -70,6 +79,7 @@ function makeDeps(overrides: Partial<IntentDispatchDeps> = {}): IntentDispatchDe
|
|
|
70
79
|
setReviewWidth: mock(),
|
|
71
80
|
setRailTab: mock(),
|
|
72
81
|
setFocusedAnnotationId: mock(),
|
|
82
|
+
setSelectedCurationId: mock(),
|
|
73
83
|
setPulsedAnnotationId: mock(),
|
|
74
84
|
selectCardFromDocument: mock(),
|
|
75
85
|
runEditorHandOff: mock(),
|
|
@@ -88,7 +98,9 @@ describe("move", () => {
|
|
|
88
98
|
dispatch({ type: "move", to: "down" });
|
|
89
99
|
|
|
90
100
|
// Assert
|
|
91
|
-
const advance = (deps.setCursor as ReturnType<typeof mock>).mock.calls[0]![0] as (
|
|
101
|
+
const advance = (deps.setCursor as ReturnType<typeof mock>).mock.calls[0]![0] as (
|
|
102
|
+
current: number,
|
|
103
|
+
) => number;
|
|
92
104
|
expect(advance(2)).toBe(2); // already at the end, stays
|
|
93
105
|
expect(advance(0)).toBe(1);
|
|
94
106
|
});
|
|
@@ -134,7 +146,10 @@ describe("annotation navigation", () => {
|
|
|
134
146
|
test("cycling skips annotations a revision already addressed", () => {
|
|
135
147
|
// Arrange: a2 is addressed, so next from a1 lands on a3
|
|
136
148
|
const session = sessionWith(["a1", "a2", "a3"]);
|
|
137
|
-
(session.annotations[1] as { resolution?: object }).resolution = {
|
|
149
|
+
(session.annotations[1] as { resolution?: object }).resolution = {
|
|
150
|
+
revision: 2,
|
|
151
|
+
source: "agent",
|
|
152
|
+
};
|
|
138
153
|
const deps = makeDeps({ session, focusedAnnotationId: "a1" });
|
|
139
154
|
const dispatch = createIntentDispatch(deps);
|
|
140
155
|
|
|
@@ -156,7 +171,105 @@ describe("cycleVerdict", () => {
|
|
|
156
171
|
dispatch({ type: "cycleVerdict", direction: 1 });
|
|
157
172
|
|
|
158
173
|
// Assert
|
|
159
|
-
expect(deps.setMode).toHaveBeenCalledWith({
|
|
174
|
+
expect(deps.setMode).toHaveBeenCalledWith({
|
|
175
|
+
type: "submit",
|
|
176
|
+
verdict: "request_changes",
|
|
177
|
+
summary: "",
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("marker-actions popover", () => {
|
|
183
|
+
const span = { displayIndex: 3, wordIndex: 0, wordEnd: 0, start: 2, end: 9 };
|
|
184
|
+
|
|
185
|
+
test("spanCut cuts the span's block and closes the popover", () => {
|
|
186
|
+
// Arrange
|
|
187
|
+
const deps = makeDeps({ mode: { type: "span", span } });
|
|
188
|
+
const dispatch = createIntentDispatch(deps);
|
|
189
|
+
|
|
190
|
+
// Act
|
|
191
|
+
dispatch({ type: "spanCut" });
|
|
192
|
+
|
|
193
|
+
// Assert
|
|
194
|
+
expect(deps.controller.cut).toHaveBeenCalledWith(3);
|
|
195
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("openSpanActions enters the list at index 0", () => {
|
|
199
|
+
// Arrange
|
|
200
|
+
const deps = makeDeps({ mode: { type: "span", span } });
|
|
201
|
+
const dispatch = createIntentDispatch(deps);
|
|
202
|
+
|
|
203
|
+
// Act
|
|
204
|
+
dispatch({ type: "openSpanActions" });
|
|
205
|
+
|
|
206
|
+
// Assert
|
|
207
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "spanActions", span, index: 0 });
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("moveSpanAction clamps within the actions list", () => {
|
|
211
|
+
// Arrange
|
|
212
|
+
const deps = makeDeps({
|
|
213
|
+
mode: { type: "spanActions", span, index: 1 },
|
|
214
|
+
quickActions: [{ prompt: "one" }, { prompt: "two" }],
|
|
215
|
+
});
|
|
216
|
+
const dispatch = createIntentDispatch(deps);
|
|
217
|
+
|
|
218
|
+
// Act
|
|
219
|
+
dispatch({ type: "moveSpanAction", direction: 1 });
|
|
220
|
+
|
|
221
|
+
// Assert - already at the last row, so the index holds
|
|
222
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "spanActions", span, index: 1 });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("pickSpanAction inserts the preset comment and returns to normal", () => {
|
|
226
|
+
// Arrange
|
|
227
|
+
const annotate = mock(() => "an_new");
|
|
228
|
+
const deps = makeDeps({
|
|
229
|
+
mode: { type: "spanActions", span, index: 1 },
|
|
230
|
+
session: sessionWith([]),
|
|
231
|
+
quickActions: [{ prompt: "Needs a test" }, { prompt: "YAGNI", metadata: "cut scope" }],
|
|
232
|
+
controller: { ...baseController(), annotate } as unknown as IntentDispatchDeps["controller"],
|
|
233
|
+
});
|
|
234
|
+
const dispatch = createIntentDispatch(deps);
|
|
235
|
+
|
|
236
|
+
// Act
|
|
237
|
+
dispatch({ type: "pickSpanAction" });
|
|
238
|
+
|
|
239
|
+
// Assert - metadata joins the prompt with a blank line
|
|
240
|
+
expect(annotate).toHaveBeenCalledWith("comment", 3, 2, 9, "YAGNI\n\ncut scope");
|
|
241
|
+
expect(deps.setFocusedAnnotationId).toHaveBeenCalledWith("an_new");
|
|
242
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "normal" });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("pickSpanAction honors an explicit index (mouse click)", () => {
|
|
246
|
+
// Arrange
|
|
247
|
+
const annotate = mock(() => "an_new");
|
|
248
|
+
const deps = makeDeps({
|
|
249
|
+
mode: { type: "spanActions", span, index: 0 },
|
|
250
|
+
session: sessionWith([]),
|
|
251
|
+
quickActions: [{ prompt: "Needs a test" }, { prompt: "Extract the duplication" }],
|
|
252
|
+
controller: { ...baseController(), annotate } as unknown as IntentDispatchDeps["controller"],
|
|
253
|
+
});
|
|
254
|
+
const dispatch = createIntentDispatch(deps);
|
|
255
|
+
|
|
256
|
+
// Act
|
|
257
|
+
dispatch({ type: "pickSpanAction", index: 1 });
|
|
258
|
+
|
|
259
|
+
// Assert
|
|
260
|
+
expect(annotate).toHaveBeenCalledWith("comment", 3, 2, 9, "Extract the duplication");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("closeSpanActions returns to the span toolbar", () => {
|
|
264
|
+
// Arrange
|
|
265
|
+
const deps = makeDeps({ mode: { type: "spanActions", span, index: 2 } });
|
|
266
|
+
const dispatch = createIntentDispatch(deps);
|
|
267
|
+
|
|
268
|
+
// Act
|
|
269
|
+
dispatch({ type: "closeSpanActions" });
|
|
270
|
+
|
|
271
|
+
// Assert
|
|
272
|
+
expect(deps.setMode).toHaveBeenCalledWith({ type: "span", span });
|
|
160
273
|
});
|
|
161
274
|
});
|
|
162
275
|
|
|
@@ -202,6 +315,65 @@ describe("share", () => {
|
|
|
202
315
|
});
|
|
203
316
|
});
|
|
204
317
|
|
|
318
|
+
describe("restoreCuration", () => {
|
|
319
|
+
const items = [
|
|
320
|
+
{
|
|
321
|
+
id: "diff:f#0#hunk",
|
|
322
|
+
source: "diff" as const,
|
|
323
|
+
label: "f:1 - hunk",
|
|
324
|
+
preview: [],
|
|
325
|
+
revealIndex: 1,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "diff:f#1#2",
|
|
329
|
+
source: "diff" as const,
|
|
330
|
+
label: "f:9 - change",
|
|
331
|
+
preview: [],
|
|
332
|
+
revealIndex: 9,
|
|
333
|
+
},
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
test("restores the selected item and clears the selection", () => {
|
|
337
|
+
// Arrange
|
|
338
|
+
const deps = makeDeps({ selectedCurationId: "diff:f#1#2" });
|
|
339
|
+
(deps.controller.curationItems as ReturnType<typeof mock>).mockReturnValue(items);
|
|
340
|
+
const dispatch = createIntentDispatch(deps);
|
|
341
|
+
|
|
342
|
+
// Act
|
|
343
|
+
dispatch({ type: "restoreCuration" });
|
|
344
|
+
|
|
345
|
+
// Assert
|
|
346
|
+
expect(deps.controller.restoreCuration).toHaveBeenCalledWith("diff:f#1#2");
|
|
347
|
+
expect(deps.setSelectedCurationId).toHaveBeenCalledWith(undefined);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("with nothing selected, undoes the last removal", () => {
|
|
351
|
+
// Arrange
|
|
352
|
+
const deps = makeDeps({ selectedCurationId: undefined });
|
|
353
|
+
(deps.controller.curationItems as ReturnType<typeof mock>).mockReturnValue(items);
|
|
354
|
+
const dispatch = createIntentDispatch(deps);
|
|
355
|
+
|
|
356
|
+
// Act
|
|
357
|
+
dispatch({ type: "restoreCuration" });
|
|
358
|
+
|
|
359
|
+
// Assert
|
|
360
|
+
expect(deps.controller.restoreCuration).toHaveBeenCalledWith("diff:f#1#2");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("does nothing when there is nothing curated out", () => {
|
|
364
|
+
// Arrange
|
|
365
|
+
const deps = makeDeps();
|
|
366
|
+
(deps.controller.curationItems as ReturnType<typeof mock>).mockReturnValue([]);
|
|
367
|
+
const dispatch = createIntentDispatch(deps);
|
|
368
|
+
|
|
369
|
+
// Act
|
|
370
|
+
dispatch({ type: "restoreCuration" });
|
|
371
|
+
|
|
372
|
+
// Assert
|
|
373
|
+
expect(deps.controller.restoreCuration).not.toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
205
377
|
describe("exit", () => {
|
|
206
378
|
test("calls the exit hook with 0", () => {
|
|
207
379
|
// Arrange
|
|
@@ -226,7 +398,11 @@ describe("inbox delete", () => {
|
|
|
226
398
|
createIntentDispatch(deps)({ type: "requestDeleteSession" });
|
|
227
399
|
|
|
228
400
|
// Assert
|
|
229
|
-
expect(deps.setMode).toHaveBeenCalledWith({
|
|
401
|
+
expect(deps.setMode).toHaveBeenCalledWith({
|
|
402
|
+
type: "confirmDelete",
|
|
403
|
+
sessionId: "ses_1",
|
|
404
|
+
title: "Plan A",
|
|
405
|
+
});
|
|
230
406
|
});
|
|
231
407
|
|
|
232
408
|
test("confirmDialog deletes the session and closes", () => {
|
|
@@ -246,13 +422,21 @@ describe("rename author", () => {
|
|
|
246
422
|
test("openRename seeds the prompt with the author's current local name", () => {
|
|
247
423
|
// Arrange
|
|
248
424
|
const session = { annotations: [{ id: "a1", author: "SHA256:x" }] } as never;
|
|
249
|
-
const deps = makeDeps({
|
|
425
|
+
const deps = makeDeps({
|
|
426
|
+
session,
|
|
427
|
+
focusedAnnotationId: "a1",
|
|
428
|
+
authorNames: { "SHA256:x": "Alex" },
|
|
429
|
+
});
|
|
250
430
|
|
|
251
431
|
// Act
|
|
252
432
|
createIntentDispatch(deps)({ type: "openRename" });
|
|
253
433
|
|
|
254
434
|
// Assert
|
|
255
|
-
expect(deps.setMode).toHaveBeenCalledWith({
|
|
435
|
+
expect(deps.setMode).toHaveBeenCalledWith({
|
|
436
|
+
type: "rename",
|
|
437
|
+
authorId: "SHA256:x",
|
|
438
|
+
text: "Alex",
|
|
439
|
+
});
|
|
256
440
|
});
|
|
257
441
|
|
|
258
442
|
test("openRename on your own note does nothing but explain", () => {
|
package/src/intent-dispatch.ts
CHANGED
|
@@ -13,32 +13,55 @@ import { displayText, spanKey, startSpan, type DisplayBlock, type SpanState } fr
|
|
|
13
13
|
import type { DiffRow } from "./view-diff";
|
|
14
14
|
import type { ReviewController } from "./session-controller";
|
|
15
15
|
import type { Intent } from "./keymap";
|
|
16
|
+
import type { QuickAction } from "./config";
|
|
16
17
|
import type { PlanSheetHandle } from "./components/PlanSheet";
|
|
17
18
|
import { VERDICTS } from "./components/ConfirmCard";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
REVIEW_RESIZE_STEP,
|
|
21
|
+
cycleReviewPanelMode,
|
|
22
|
+
resolveReviewWidth,
|
|
23
|
+
type ReviewPanelMode,
|
|
24
|
+
} from "./review-panel";
|
|
19
25
|
|
|
20
26
|
/** The one overlay/mode the TUI is in; every compose/submit/edit flow is a variant. */
|
|
21
27
|
export type Mode =
|
|
22
28
|
| { type: "normal" }
|
|
23
29
|
| { type: "span"; span: SpanState }
|
|
24
|
-
| { type: "
|
|
30
|
+
| { type: "spanActions"; span: SpanState; index: number }
|
|
31
|
+
| {
|
|
32
|
+
type: "compose";
|
|
33
|
+
kind: "comment";
|
|
34
|
+
displayIndex: number;
|
|
35
|
+
start: number;
|
|
36
|
+
end: number;
|
|
37
|
+
text: string;
|
|
38
|
+
}
|
|
25
39
|
| { type: "railEdit"; id: string; text: string }
|
|
26
40
|
| { type: "submit"; verdict: VerdictKind; summary: string }
|
|
27
41
|
| { type: "confirmDelete"; sessionId: string; title: string }
|
|
28
42
|
| { type: "rename"; authorId: string; text: string }
|
|
29
43
|
| { type: "nameSelf"; text: string };
|
|
30
44
|
|
|
45
|
+
/** The marked span for span mode and its quick-actions sub-mode; null otherwise. */
|
|
46
|
+
export function activeSpanState(mode: Mode): SpanState | null {
|
|
47
|
+
return mode.type === "span" || mode.type === "spanActions" ? mode.span : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
/**
|
|
32
51
|
* Annotations that still count as feedback: agent notes never do, and an
|
|
33
52
|
* annotation a revision already addressed is settled - it neither blocks the
|
|
34
53
|
* verdict default nor re-enters the next feedback document.
|
|
35
54
|
*/
|
|
36
55
|
export function reviewerAnnotations(session: ReviewSession) {
|
|
37
|
-
return session.annotations.filter(
|
|
56
|
+
return session.annotations.filter(
|
|
57
|
+
(annotation) => !isAgentNote(annotation) && !isAddressed(annotation),
|
|
58
|
+
);
|
|
38
59
|
}
|
|
39
60
|
|
|
40
61
|
export function defaultVerdict(session: ReviewSession): VerdictKind {
|
|
41
|
-
return reviewerAnnotations(session).length || session.workingCopy !== undefined
|
|
62
|
+
return reviewerAnnotations(session).length || session.workingCopy !== undefined
|
|
63
|
+
? "request_changes"
|
|
64
|
+
: "approve";
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
/**
|
|
@@ -63,8 +86,12 @@ export interface IntentDispatchDeps {
|
|
|
63
86
|
reviewWidth: number;
|
|
64
87
|
terminalWidth: number;
|
|
65
88
|
focusedAnnotationId: string | undefined;
|
|
89
|
+
/** The curation item selected in the rail, if any; the undo target when set. */
|
|
90
|
+
selectedCurationId: string | undefined;
|
|
66
91
|
/** Planner-local author renames, for seeding the rename prompt. */
|
|
67
92
|
authorNames: Record<string, string>;
|
|
93
|
+
/** Marker-popover quick actions, in list order; picking one inserts a preset comment. */
|
|
94
|
+
quickActions: QuickAction[];
|
|
68
95
|
/** Persist an author rename and update the live overrides (App-owned). */
|
|
69
96
|
renameAuthor: (id: string, name: string) => void;
|
|
70
97
|
|
|
@@ -79,6 +106,7 @@ export interface IntentDispatchDeps {
|
|
|
79
106
|
setReviewWidth: Dispatch<SetStateAction<number>>;
|
|
80
107
|
setRailTab: Dispatch<SetStateAction<"review" | "agent">>;
|
|
81
108
|
setFocusedAnnotationId: Dispatch<SetStateAction<string | undefined>>;
|
|
109
|
+
setSelectedCurationId: Dispatch<SetStateAction<string | undefined>>;
|
|
82
110
|
setPulsedAnnotationId: Dispatch<SetStateAction<string | null>>;
|
|
83
111
|
|
|
84
112
|
selectCardFromDocument: (annotationId: string) => void;
|
|
@@ -103,7 +131,9 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
103
131
|
reviewWidth,
|
|
104
132
|
terminalWidth,
|
|
105
133
|
focusedAnnotationId,
|
|
134
|
+
selectedCurationId,
|
|
106
135
|
authorNames,
|
|
136
|
+
quickActions,
|
|
107
137
|
renameAuthor,
|
|
108
138
|
liveInput,
|
|
109
139
|
reviewWidthRef,
|
|
@@ -115,6 +145,7 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
115
145
|
setReviewWidth,
|
|
116
146
|
setRailTab,
|
|
117
147
|
setFocusedAnnotationId,
|
|
148
|
+
setSelectedCurationId,
|
|
118
149
|
setPulsedAnnotationId,
|
|
119
150
|
selectCardFromDocument,
|
|
120
151
|
runEditorHandOff,
|
|
@@ -137,7 +168,11 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
137
168
|
}
|
|
138
169
|
case "inboxMove": {
|
|
139
170
|
const navigableCount = inbox?.length ?? 0;
|
|
140
|
-
setInboxCursor((current) =>
|
|
171
|
+
setInboxCursor((current) =>
|
|
172
|
+
intent.to === "down"
|
|
173
|
+
? Math.min(navigableCount - 1, current + 1)
|
|
174
|
+
: Math.max(0, current - 1),
|
|
175
|
+
);
|
|
141
176
|
return;
|
|
142
177
|
}
|
|
143
178
|
case "openSession": {
|
|
@@ -147,13 +182,25 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
147
182
|
}
|
|
148
183
|
case "requestDeleteSession": {
|
|
149
184
|
const selected = inbox?.[inboxCursor];
|
|
150
|
-
if (selected)
|
|
185
|
+
if (selected)
|
|
186
|
+
setMode({
|
|
187
|
+
type: "confirmDelete",
|
|
188
|
+
sessionId: selected.id,
|
|
189
|
+
title: selected.artifact.meta.title ?? selected.id,
|
|
190
|
+
});
|
|
151
191
|
return;
|
|
152
192
|
}
|
|
153
193
|
case "openRename": {
|
|
154
|
-
const focused = session?.annotations.find(
|
|
155
|
-
|
|
156
|
-
|
|
194
|
+
const focused = session?.annotations.find(
|
|
195
|
+
(annotation) => annotation.id === focusedAnnotationId,
|
|
196
|
+
);
|
|
197
|
+
if (!focused?.author)
|
|
198
|
+
return void controller.setStatus("that is your own note - nothing to rename");
|
|
199
|
+
return void setMode({
|
|
200
|
+
type: "rename",
|
|
201
|
+
authorId: focused.author,
|
|
202
|
+
text: authorNames[focused.author] ?? "",
|
|
203
|
+
});
|
|
157
204
|
}
|
|
158
205
|
case "confirmDialog": {
|
|
159
206
|
if (mode.type === "confirmDelete") controller.deleteSession(mode.sessionId);
|
|
@@ -170,10 +217,52 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
170
217
|
}
|
|
171
218
|
case "spanKey":
|
|
172
219
|
if (mode.type === "span") {
|
|
173
|
-
const span = spanKey(
|
|
220
|
+
const span = spanKey(
|
|
221
|
+
mode.span,
|
|
222
|
+
intent.name,
|
|
223
|
+
displayText(display[mode.span.displayIndex]!),
|
|
224
|
+
);
|
|
174
225
|
setMode({ type: "span", span });
|
|
175
226
|
}
|
|
176
227
|
return;
|
|
228
|
+
case "spanCut":
|
|
229
|
+
// the block the span sits in, cut whole (partial-span cut is not modeled)
|
|
230
|
+
if (mode.type === "span") {
|
|
231
|
+
controller.cut(mode.span.displayIndex);
|
|
232
|
+
setMode({ type: "normal" });
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
case "openSpanActions":
|
|
236
|
+
if (mode.type === "span") setMode({ type: "spanActions", span: mode.span, index: 0 });
|
|
237
|
+
return;
|
|
238
|
+
case "moveSpanAction":
|
|
239
|
+
if (mode.type === "spanActions") {
|
|
240
|
+
const index = Math.max(
|
|
241
|
+
0,
|
|
242
|
+
Math.min(quickActions.length - 1, mode.index + intent.direction),
|
|
243
|
+
);
|
|
244
|
+
setMode({ ...mode, index });
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
case "closeSpanActions":
|
|
248
|
+
if (mode.type === "spanActions") setMode({ type: "span", span: mode.span });
|
|
249
|
+
return;
|
|
250
|
+
case "pickSpanAction": {
|
|
251
|
+
if (mode.type !== "spanActions") return;
|
|
252
|
+
const action = quickActions[intent.index ?? mode.index];
|
|
253
|
+
if (session && action) {
|
|
254
|
+
const body = action.metadata ? `${action.prompt}\n\n${action.metadata}` : action.prompt;
|
|
255
|
+
const annotationId = controller.annotate(
|
|
256
|
+
"comment",
|
|
257
|
+
mode.span.displayIndex,
|
|
258
|
+
mode.span.start,
|
|
259
|
+
mode.span.end,
|
|
260
|
+
body,
|
|
261
|
+
);
|
|
262
|
+
if (annotationId) setFocusedAnnotationId(annotationId);
|
|
263
|
+
}
|
|
264
|
+
return void setMode({ type: "normal" });
|
|
265
|
+
}
|
|
177
266
|
case "openCompose": {
|
|
178
267
|
liveInput.current = "";
|
|
179
268
|
if (intent.from === "span" && mode.type === "span") {
|
|
@@ -187,7 +276,15 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
187
276
|
});
|
|
188
277
|
} else if (isDiff) {
|
|
189
278
|
const row = rows[cursor];
|
|
190
|
-
if (row)
|
|
279
|
+
if (row)
|
|
280
|
+
setMode({
|
|
281
|
+
type: "compose",
|
|
282
|
+
kind: intent.kind,
|
|
283
|
+
displayIndex: cursor,
|
|
284
|
+
start: 0,
|
|
285
|
+
end: row.text.length,
|
|
286
|
+
text: "",
|
|
287
|
+
});
|
|
191
288
|
} else {
|
|
192
289
|
// a mouse drag leaves a native selection; it wins over the cursor block
|
|
193
290
|
const native = planSheetRef.current?.readSelection() ?? null;
|
|
@@ -195,7 +292,15 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
195
292
|
setMode({ type: "compose", kind: intent.kind, ...native, text: "" });
|
|
196
293
|
} else {
|
|
197
294
|
const block = display[cursor];
|
|
198
|
-
if (block)
|
|
295
|
+
if (block)
|
|
296
|
+
setMode({
|
|
297
|
+
type: "compose",
|
|
298
|
+
kind: intent.kind,
|
|
299
|
+
displayIndex: cursor,
|
|
300
|
+
start: 0,
|
|
301
|
+
end: displayText(block).length,
|
|
302
|
+
text: "",
|
|
303
|
+
});
|
|
199
304
|
}
|
|
200
305
|
}
|
|
201
306
|
return;
|
|
@@ -213,6 +318,19 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
213
318
|
return controller.share();
|
|
214
319
|
case "cut":
|
|
215
320
|
return controller.cut(cursor);
|
|
321
|
+
case "rejectHunk":
|
|
322
|
+
return controller.toggleRejectHunk(cursor);
|
|
323
|
+
case "rejectChange":
|
|
324
|
+
return controller.toggleRejectChange(cursor);
|
|
325
|
+
case "restoreCuration": {
|
|
326
|
+
// undo the selected curated-out item, or the last rejected when none is
|
|
327
|
+
// selected, so a bare `u` reads as "undo my last curation"
|
|
328
|
+
const items = controller.curationItems();
|
|
329
|
+
if (!items.length) return;
|
|
330
|
+
const targetId = selectedCurationId ?? items[items.length - 1]!.id;
|
|
331
|
+
controller.restoreCuration(targetId);
|
|
332
|
+
return void setSelectedCurationId(undefined);
|
|
333
|
+
}
|
|
216
334
|
case "edit":
|
|
217
335
|
return runEditorHandOff();
|
|
218
336
|
case "editCard":
|
|
@@ -221,13 +339,18 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
221
339
|
case "nextAnnotation":
|
|
222
340
|
case "prevAnnotation": {
|
|
223
341
|
// cycle open cards only - addressed ones are out of the rail
|
|
224
|
-
const annotations = (session?.annotations ?? []).filter(
|
|
342
|
+
const annotations = (session?.annotations ?? []).filter(
|
|
343
|
+
(annotation) => !isAddressed(annotation),
|
|
344
|
+
);
|
|
225
345
|
if (!annotations.length) return;
|
|
226
|
-
const focusedIndex = annotations.findIndex(
|
|
346
|
+
const focusedIndex = annotations.findIndex(
|
|
347
|
+
(annotation) => annotation.id === focusedAnnotationId,
|
|
348
|
+
);
|
|
227
349
|
const nextIndex =
|
|
228
350
|
focusedIndex === -1
|
|
229
351
|
? 0
|
|
230
|
-
: (focusedIndex + (intent.type === "nextAnnotation" ? 1 : -1) + annotations.length) %
|
|
352
|
+
: (focusedIndex + (intent.type === "nextAnnotation" ? 1 : -1) + annotations.length) %
|
|
353
|
+
annotations.length;
|
|
231
354
|
return void selectCardFromDocument(annotations[nextIndex]!.id);
|
|
232
355
|
}
|
|
233
356
|
case "walkStart":
|
|
@@ -259,7 +382,13 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
259
382
|
}
|
|
260
383
|
if (mode.type !== "compose") return;
|
|
261
384
|
if (session && body) {
|
|
262
|
-
const annotationId = controller.annotate(
|
|
385
|
+
const annotationId = controller.annotate(
|
|
386
|
+
mode.kind,
|
|
387
|
+
mode.displayIndex,
|
|
388
|
+
mode.start,
|
|
389
|
+
mode.end,
|
|
390
|
+
body,
|
|
391
|
+
);
|
|
263
392
|
if (annotationId) setFocusedAnnotationId(annotationId);
|
|
264
393
|
}
|
|
265
394
|
return void setMode({ type: "normal" });
|
|
@@ -269,7 +398,8 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
269
398
|
return void setMode({ type: "normal" });
|
|
270
399
|
case "cycleVerdict": {
|
|
271
400
|
if (mode.type !== "submit") return;
|
|
272
|
-
const verdictIndex =
|
|
401
|
+
const verdictIndex =
|
|
402
|
+
(VERDICTS.indexOf(mode.verdict) + intent.direction + VERDICTS.length) % VERDICTS.length;
|
|
273
403
|
return void setMode({ ...mode, verdict: VERDICTS[verdictIndex]! });
|
|
274
404
|
}
|
|
275
405
|
case "finishReview":
|
|
@@ -285,7 +415,10 @@ export function createIntentDispatch(deps: IntentDispatchDeps): (intent: Intent)
|
|
|
285
415
|
}
|
|
286
416
|
case "resizeReviewPanel": {
|
|
287
417
|
if (reviewMode !== "expanded") return;
|
|
288
|
-
const next = resolveReviewWidth(
|
|
418
|
+
const next = resolveReviewWidth(
|
|
419
|
+
reviewWidth + intent.direction * REVIEW_RESIZE_STEP,
|
|
420
|
+
terminalWidth,
|
|
421
|
+
);
|
|
289
422
|
reviewWidthRef.current = next;
|
|
290
423
|
setReviewWidth(next);
|
|
291
424
|
return controller.saveReviewPanel({ width: next });
|