@cueloop/client 0.1.0-alpha.64 → 0.1.0-alpha.65
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 +5 -5
- package/src/App.autoclose.test.tsx +2 -2
- package/src/App.export.test.tsx +2 -2
- package/src/App.plan-review.test.tsx +76 -247
- package/src/App.readonly.test.tsx +80 -35
- package/src/App.rendering.test.tsx +5 -6
- package/src/App.review-panel.test.tsx +29 -32
- package/src/App.submit-confirm.test.tsx +27 -12
- package/src/App.test.tsx +73 -49
- package/src/App.toast.test.tsx +17 -8
- package/src/App.tree.test.tsx +224 -0
- package/src/App.tsx +120 -84
- package/src/app-screens.tsx +27 -1
- package/src/app-view-model.ts +4 -96
- package/src/components/AnnotationCard.tsx +2 -2
- package/src/components/KeybindsDialog.tsx +61 -17
- package/src/components/ReviewPanel.stories.tsx +14 -0
- package/src/components/ReviewRail.stories.tsx +14 -0
- package/src/components/ReviewRail.tsx +13 -6
- package/src/components/ThreadView.selection.test.tsx +812 -0
- package/src/components/ThreadView.stories.tsx +26 -0
- package/src/components/ThreadView.tsx +1620 -0
- package/src/components/TreePane.stories.tsx +106 -0
- package/src/components/TreePane.tsx +156 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +120 -172
- package/src/diff-hunk-curate.ts +4 -104
- package/src/index.ts +1 -0
- package/src/intent-dispatch.test.ts +136 -3
- package/src/intent-dispatch.ts +92 -24
- package/src/key-bindings.test.ts +1 -1
- package/src/keymap.test.ts +4 -4
- package/src/keymap.ts +14 -7
- package/src/session-controller.curate.test.ts +89 -2
- package/src/session-controller.share.test.ts +195 -55
- package/src/session-controller.tree.test.ts +228 -0
- package/src/session-controller.ts +322 -68
- package/src/share.ts +114 -16
- package/src/test-support.test.ts +193 -1
- package/src/test-support.ts +64 -0
- package/src/thread-chords.test.ts +154 -0
- package/src/thread-chords.ts +144 -0
- package/src/thread-selection.test.ts +210 -0
- package/src/thread-selection.ts +243 -0
- package/src/tree-view.test.ts +128 -0
- package/src/tree-view.ts +148 -0
- package/src/view-plan.ts +29 -12
- package/src/components/PlanSheet.stories.tsx +0 -113
- package/src/components/PlanSheet.tsx +0 -419
- package/src/components/plan-sheet-run-style.ts +0 -88
package/src/app-screens.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import React, { type Dispatch, type SetStateAction } from "react";
|
|
|
2
2
|
import type { ReviewSession, VerdictKind } from "@cueloop/schema";
|
|
3
3
|
import { returnPaneFor } from "@cueloop/schema";
|
|
4
4
|
import type { Theme } from "./theme";
|
|
5
|
-
import type { Mode } from "./intent-dispatch";
|
|
5
|
+
import type { Mode, TreeAsk } from "./intent-dispatch";
|
|
6
6
|
import type { Intent } from "./keymap";
|
|
7
7
|
import type { ReviewController, ToastState } from "./session-controller";
|
|
8
8
|
import { noteForFile } from "./walk";
|
|
@@ -195,6 +195,21 @@ export function CompletionScreen(props: {
|
|
|
195
195
|
);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/** The words each tree prompt uses; enter with an empty summary moves back without one. */
|
|
199
|
+
const TREE_PROMPTS: Record<TreeAsk, { title: string; label: string; placeholder: string }> = {
|
|
200
|
+
branch: { title: " Branch ", label: "Name for the new branch:", placeholder: "alt" },
|
|
201
|
+
label: {
|
|
202
|
+
title: " Checkpoint ",
|
|
203
|
+
label: "Name for this checkpoint:",
|
|
204
|
+
placeholder: "before the rewrite",
|
|
205
|
+
},
|
|
206
|
+
navigate: {
|
|
207
|
+
title: " Move back ",
|
|
208
|
+
label: "Summary of what you leave behind (optional):",
|
|
209
|
+
placeholder: "tried a shorter plan",
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
198
213
|
export function TrailingOverlays(props: {
|
|
199
214
|
walking: boolean;
|
|
200
215
|
walk: { index: number } | null;
|
|
@@ -264,6 +279,17 @@ export function TrailingOverlays(props: {
|
|
|
264
279
|
theme={theme}
|
|
265
280
|
/>
|
|
266
281
|
) : null}
|
|
282
|
+
{mode.type === "treePrompt" ? (
|
|
283
|
+
<PromptDialog
|
|
284
|
+
isOpen
|
|
285
|
+
title={TREE_PROMPTS[mode.ask].title}
|
|
286
|
+
label={TREE_PROMPTS[mode.ask].label}
|
|
287
|
+
value={mode.text}
|
|
288
|
+
placeholder={TREE_PROMPTS[mode.ask].placeholder}
|
|
289
|
+
onInput={(text) => setMode({ ...mode, text })}
|
|
290
|
+
theme={theme}
|
|
291
|
+
/>
|
|
292
|
+
) : null}
|
|
267
293
|
{toast ? <Toast title={toast.title} body={toast.body} theme={theme} /> : null}
|
|
268
294
|
</>
|
|
269
295
|
);
|
package/src/app-view-model.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
|
|
2
2
|
import type { ReviewSession, VerdictKind } from "@cueloop/schema";
|
|
3
|
-
import {
|
|
4
|
-
import { displayText, type DisplayBlock } from "./view-plan";
|
|
3
|
+
import { reviewerAnnotations, type Mode } from "./intent-dispatch";
|
|
5
4
|
import type { DiffRow } from "./view-diff";
|
|
6
5
|
import type { Intent, KeyState } from "./keymap";
|
|
7
|
-
import type { Completion
|
|
8
|
-
import type { QuickAction } from "./config";
|
|
6
|
+
import type { Completion } from "./session-controller";
|
|
9
7
|
import type { WalkFile } from "./walk";
|
|
10
8
|
import { viewedCount } from "./walk";
|
|
11
|
-
import type { PlanComposeState, PlanPopoverState } from "./components/PlanSheet";
|
|
12
9
|
import type { DiffComposeState } from "./components/DiffSheet";
|
|
13
10
|
import type { ConfirmCardProps } from "./components/ConfirmCard";
|
|
14
11
|
import type { RailCardEdit } from "./components/ReviewRail";
|
|
@@ -44,7 +41,8 @@ export function resolveOverlay(
|
|
|
44
41
|
if (mode.type === "compose" || mode.type === "railEdit") return "compose";
|
|
45
42
|
if (mode.type === "submit") return "submit";
|
|
46
43
|
if (mode.type === "confirmDelete") return "confirm";
|
|
47
|
-
if (mode.type === "rename" || mode.type === "nameSelf"
|
|
44
|
+
if (mode.type === "rename" || mode.type === "nameSelf" || mode.type === "treePrompt")
|
|
45
|
+
return "prompt";
|
|
48
46
|
if (mode.type === "spanActions") return "spanActions";
|
|
49
47
|
if (completionPhase === "prompt") return "completion-prompt";
|
|
50
48
|
if (completionPhase === "counting") return "completion-counting";
|
|
@@ -130,49 +128,12 @@ export function buildRenderFlags(params: {
|
|
|
130
128
|
};
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
export function buildActiveSpan(
|
|
134
|
-
mode: Mode,
|
|
135
|
-
isDiff: boolean,
|
|
136
|
-
): { displayIndex: number; start: number; end: number } | null {
|
|
137
|
-
const markedSpan = activeSpanState(mode);
|
|
138
|
-
|
|
139
|
-
if (markedSpan)
|
|
140
|
-
return { displayIndex: markedSpan.displayIndex, start: markedSpan.start, end: markedSpan.end };
|
|
141
|
-
if (mode.type === "compose" && !isDiff)
|
|
142
|
-
return { displayIndex: mode.displayIndex, start: mode.start, end: mode.end };
|
|
143
|
-
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
131
|
interface DraftHandlerDeps {
|
|
148
132
|
liveInput: MutableRefObject<string>;
|
|
149
133
|
setMode: Dispatch<SetStateAction<Mode>>;
|
|
150
134
|
dispatch: (intent: Intent) => void;
|
|
151
135
|
}
|
|
152
136
|
|
|
153
|
-
export function buildComposeState(
|
|
154
|
-
deps: DraftHandlerDeps & { mode: Mode; isDiff: boolean; display: DisplayBlock[] },
|
|
155
|
-
): PlanComposeState | null {
|
|
156
|
-
const { mode, isDiff, display, liveInput, setMode, dispatch } = deps;
|
|
157
|
-
|
|
158
|
-
if (mode.type !== "compose" || isDiff) return null;
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
kind: mode.kind,
|
|
162
|
-
displayIndex: mode.displayIndex,
|
|
163
|
-
quote: displayText(display[mode.displayIndex]!).slice(mode.start, mode.end),
|
|
164
|
-
draft: {
|
|
165
|
-
text: mode.text,
|
|
166
|
-
onInput: (text: string) => {
|
|
167
|
-
liveInput.current = text;
|
|
168
|
-
setMode({ ...mode, text });
|
|
169
|
-
},
|
|
170
|
-
onSave: () => dispatch({ type: "saveCompose" }),
|
|
171
|
-
onCancel: () => dispatch({ type: "closeOverlay" }),
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
137
|
export function buildDiffComposeState(
|
|
177
138
|
deps: DraftHandlerDeps & { mode: Mode; isDiff: boolean; rows: DiffRow[] },
|
|
178
139
|
): DiffComposeState | null {
|
|
@@ -196,59 +157,6 @@ export function buildDiffComposeState(
|
|
|
196
157
|
};
|
|
197
158
|
}
|
|
198
159
|
|
|
199
|
-
export function buildPopoverState(deps: {
|
|
200
|
-
mode: Mode;
|
|
201
|
-
isDiff: boolean;
|
|
202
|
-
quickActions: QuickAction[];
|
|
203
|
-
isOwner: boolean;
|
|
204
|
-
observer: boolean;
|
|
205
|
-
resolved: boolean;
|
|
206
|
-
controller: ReviewController;
|
|
207
|
-
dispatch: (intent: Intent) => void;
|
|
208
|
-
}): PlanPopoverState | null {
|
|
209
|
-
const { mode, isDiff, quickActions, isOwner, observer, resolved, controller, dispatch } = deps;
|
|
210
|
-
const markedSpan = activeSpanState(mode);
|
|
211
|
-
|
|
212
|
-
if (!markedSpan || isDiff) return null;
|
|
213
|
-
const spanMutationBlock = (): string | null =>
|
|
214
|
-
observer ? "observer - read-only" : resolved ? "review submitted - read-only" : null;
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
displayIndex: markedSpan.displayIndex,
|
|
218
|
-
view: mode.type === "spanActions" ? ("actions" as const) : ("toolbar" as const),
|
|
219
|
-
actions: quickActions,
|
|
220
|
-
actionIndex: mode.type === "spanActions" ? mode.index : 0,
|
|
221
|
-
canCut: isOwner,
|
|
222
|
-
onComment: () => {
|
|
223
|
-
const blocked = spanMutationBlock();
|
|
224
|
-
|
|
225
|
-
if (blocked) return controller.setStatus(blocked);
|
|
226
|
-
dispatch({ type: "openCompose", kind: "comment", from: "span" });
|
|
227
|
-
},
|
|
228
|
-
onCut: () => {
|
|
229
|
-
const blocked = spanMutationBlock();
|
|
230
|
-
|
|
231
|
-
if (blocked) return controller.setStatus(blocked);
|
|
232
|
-
if (!isOwner) return;
|
|
233
|
-
dispatch({ type: "spanCut" });
|
|
234
|
-
},
|
|
235
|
-
onOpenActions: () => {
|
|
236
|
-
const blocked = spanMutationBlock();
|
|
237
|
-
|
|
238
|
-
if (blocked) return controller.setStatus(blocked);
|
|
239
|
-
dispatch({ type: "openSpanActions" });
|
|
240
|
-
},
|
|
241
|
-
onClose: () => dispatch({ type: "closeOverlay" }),
|
|
242
|
-
onPickAction: (index: number) => {
|
|
243
|
-
const blocked = spanMutationBlock();
|
|
244
|
-
|
|
245
|
-
if (blocked) return controller.setStatus(blocked);
|
|
246
|
-
dispatch({ type: "pickSpanAction", index });
|
|
247
|
-
},
|
|
248
|
-
onBack: () => dispatch({ type: "closeSpanActions" }),
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
160
|
export function buildSubmitConfirmState(
|
|
253
161
|
deps: DraftHandlerDeps & {
|
|
254
162
|
mode: Mode;
|
|
@@ -181,11 +181,11 @@ export function AnnotationCard({
|
|
|
181
181
|
);
|
|
182
182
|
|
|
183
183
|
if (draft) {
|
|
184
|
-
const
|
|
184
|
+
const primitive = "comment on";
|
|
185
185
|
|
|
186
186
|
return (
|
|
187
187
|
<Card
|
|
188
|
-
title={` ${
|
|
188
|
+
title={` ${primitive} "${truncateToSingleLine(quote, 40)}" `}
|
|
189
189
|
contentRows={editorRowCount + 1}
|
|
190
190
|
borderColor={kindColor}
|
|
191
191
|
backgroundColor="transparent"
|
|
@@ -10,13 +10,52 @@ export interface KeybindsDialogProps {
|
|
|
10
10
|
theme?: Theme;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** Rows a section takes: its title, its entries, and the blank line after it. */
|
|
14
|
+
function sectionRows(section: CheatsheetSection): number {
|
|
15
|
+
return 1 + section.entries.length + 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Split the sections over `count` columns, filling each column to the
|
|
20
|
+
* balanced share of rows before starting the next, so a long cheatsheet
|
|
21
|
+
* reads top to bottom, then left to right.
|
|
22
|
+
*/
|
|
23
|
+
function columnsOf(sections: CheatsheetSection[], count: number): CheatsheetSection[][] {
|
|
24
|
+
const total = sections.reduce((sum, section) => sum + sectionRows(section), 0);
|
|
25
|
+
const share = Math.ceil(total / count);
|
|
26
|
+
const columns: CheatsheetSection[][] = [[]];
|
|
27
|
+
let filled = 0;
|
|
28
|
+
|
|
29
|
+
for (const section of sections) {
|
|
30
|
+
const column = columns[columns.length - 1]!;
|
|
31
|
+
|
|
32
|
+
if (filled > 0 && filled + sectionRows(section) > share && columns.length < count) {
|
|
33
|
+
columns.push([section]);
|
|
34
|
+
filled = sectionRows(section);
|
|
35
|
+
} else {
|
|
36
|
+
column.push(section);
|
|
37
|
+
filled += sectionRows(section);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return columns;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const COLUMN_WIDTH = 48;
|
|
45
|
+
|
|
13
46
|
export function KeybindsDialog({ sections, theme }: KeybindsDialogProps): React.ReactNode {
|
|
14
47
|
const tokens = useComponentTheme(theme);
|
|
15
48
|
const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
49
|
+
const totalRows = sections.reduce((sum, section) => sum + sectionRows(section), 0) + 1;
|
|
50
|
+
// a cheatsheet taller than the terminal folds into a second column
|
|
51
|
+
const columnCount =
|
|
52
|
+
totalRows + 3 > terminalHeight - 4 && terminalWidth >= COLUMN_WIDTH * 2 + 6 ? 2 : 1;
|
|
53
|
+
const columns = columnsOf(sections, columnCount);
|
|
54
|
+
const tallest = Math.max(
|
|
55
|
+
...columns.map((column) => column.reduce((sum, section) => sum + sectionRows(section), 0)),
|
|
56
|
+
);
|
|
57
|
+
const width = Math.min(COLUMN_WIDTH * columnCount + 4, terminalWidth - 6);
|
|
58
|
+
const height = Math.min(terminalHeight - 4, tallest + 1 + 3);
|
|
20
59
|
|
|
21
60
|
return (
|
|
22
61
|
<Dialog
|
|
@@ -36,19 +75,24 @@ export function KeybindsDialog({ sections, theme }: KeybindsDialogProps): React.
|
|
|
36
75
|
paddingTop: 1,
|
|
37
76
|
}}
|
|
38
77
|
>
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
<box style={{ flexDirection: "row", flexGrow: 1 }}>
|
|
79
|
+
{columns.map((column, columnIndex) => (
|
|
80
|
+
<box key={columnIndex} style={{ flexDirection: "column", width: COLUMN_WIDTH }}>
|
|
81
|
+
{column.map((section) => (
|
|
82
|
+
<box key={section.title} style={{ flexDirection: "column" }}>
|
|
83
|
+
<text fg={tokens.accent}>{section.title}</text>
|
|
84
|
+
{section.entries.map((entry, index) => (
|
|
85
|
+
<text key={index}>
|
|
86
|
+
<span fg={tokens.text}>{entry.keys.padEnd(12)}</span>
|
|
87
|
+
<span fg={tokens.textMuted}>{entry.label}</span>
|
|
88
|
+
</text>
|
|
89
|
+
))}
|
|
90
|
+
<text> </text>
|
|
91
|
+
</box>
|
|
92
|
+
))}
|
|
93
|
+
</box>
|
|
94
|
+
))}
|
|
95
|
+
</box>
|
|
52
96
|
<box style={{ flexDirection: "row" }}>
|
|
53
97
|
<box style={{ flexGrow: 1 }} />
|
|
54
98
|
<text fg={tokens.textDim}>[esc]</text>
|
|
@@ -7,8 +7,22 @@ import { FIXTURE_ANNOTATIONS, fixturePlanSession } from "./story-fixtures";
|
|
|
7
7
|
|
|
8
8
|
export const meta: StoryMeta = { title: "ReviewPanel" };
|
|
9
9
|
|
|
10
|
+
/** An empty Tree tab: the stories here show the review tab. */
|
|
11
|
+
const tree = {
|
|
12
|
+
rows: [],
|
|
13
|
+
canMove: true,
|
|
14
|
+
canFork: true,
|
|
15
|
+
onSelect: () => {},
|
|
16
|
+
onGo: () => {},
|
|
17
|
+
onBranch: () => {},
|
|
18
|
+
onLabel: () => {},
|
|
19
|
+
onFork: () => {},
|
|
20
|
+
onForkAndShare: () => {},
|
|
21
|
+
};
|
|
22
|
+
|
|
10
23
|
const rail = {
|
|
11
24
|
session: fixturePlanSession(),
|
|
25
|
+
tree,
|
|
12
26
|
authorNames: {},
|
|
13
27
|
selectedId: FIXTURE_ANNOTATIONS[0]!.id,
|
|
14
28
|
resolvedIds: new Set(FIXTURE_ANNOTATIONS.map((annotation) => annotation.id)),
|
|
@@ -6,8 +6,22 @@ import { FIXTURE_ANNOTATIONS, fixtureDiffSession, fixturePlanSession } from "./s
|
|
|
6
6
|
|
|
7
7
|
export const meta: StoryMeta = { title: "ReviewRail" };
|
|
8
8
|
|
|
9
|
+
/** An empty Tree tab: the stories here show the review tab. */
|
|
10
|
+
const tree = {
|
|
11
|
+
rows: [],
|
|
12
|
+
canMove: true,
|
|
13
|
+
canFork: true,
|
|
14
|
+
onSelect: () => {},
|
|
15
|
+
onGo: () => {},
|
|
16
|
+
onBranch: () => {},
|
|
17
|
+
onLabel: () => {},
|
|
18
|
+
onFork: () => {},
|
|
19
|
+
onForkAndShare: () => {},
|
|
20
|
+
};
|
|
21
|
+
|
|
9
22
|
const callbacks = {
|
|
10
23
|
curationItems: [],
|
|
24
|
+
tree,
|
|
11
25
|
onTabChange: () => {},
|
|
12
26
|
onSelectCard: () => {},
|
|
13
27
|
onActivateCard: () => {},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The right review rail: tab strip (Review/Agent), the scrollable annotation
|
|
2
|
+
* The right review rail: tab strip (Review/Tree/Agent), the scrollable annotation
|
|
3
3
|
* stack, and the pinned submit affordance. The confirm card sits OUTSIDE the
|
|
4
4
|
* scrollbox so the stack scrolls while the card stays at the rail bottom.
|
|
5
5
|
* Card reveal goes through the scrollbox's own scrollChildIntoView - no
|
|
@@ -18,11 +18,12 @@ import { Toolbar } from "./primitives/Toolbar";
|
|
|
18
18
|
import { AnnotationCard, type AnnotationDraft } from "./AnnotationCard";
|
|
19
19
|
import { ConfirmCard, type ConfirmCardProps } from "./ConfirmCard";
|
|
20
20
|
import { AgentLauncher, type AgentTerminalHandle } from "./agent-launcher";
|
|
21
|
+
import { TreePane, type TreePaneProps } from "./TreePane";
|
|
21
22
|
import { Card } from "./primitives/Card";
|
|
22
23
|
import { truncateToSingleLine } from "./truncate-text";
|
|
23
24
|
import { resolveDisplayName } from "../attribution";
|
|
24
25
|
|
|
25
|
-
export type RailTab = "review" | "agent";
|
|
26
|
+
export type RailTab = "review" | "agent" | "tree";
|
|
26
27
|
|
|
27
28
|
export interface RailCardEdit extends AnnotationDraft {
|
|
28
29
|
id: string;
|
|
@@ -65,6 +66,8 @@ export interface ReviewRailProps {
|
|
|
65
66
|
onLaunchHarness: (command: string, seedText?: string) => void;
|
|
66
67
|
/** In-tab agent terminal handle (embedded path); null when detached. Lets the app route keys to it. */
|
|
67
68
|
onAgentTerminal?: (handle: AgentTerminalHandle | null) => void;
|
|
69
|
+
/** The Tree tab: the session's history rows and the primitives that move it. */
|
|
70
|
+
tree: Omit<TreePaneProps, "theme">;
|
|
68
71
|
/** Rail column width; the app derives it from the persisted review layout. */
|
|
69
72
|
width?: number;
|
|
70
73
|
/**
|
|
@@ -95,6 +98,7 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
95
98
|
onSubmitRequest,
|
|
96
99
|
onLaunchHarness,
|
|
97
100
|
onAgentTerminal,
|
|
101
|
+
tree,
|
|
98
102
|
width = 34,
|
|
99
103
|
onCollapse,
|
|
100
104
|
theme,
|
|
@@ -188,16 +192,19 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
188
192
|
<Tabs
|
|
189
193
|
selectedKey={railTab}
|
|
190
194
|
onSelectionChange={(key) => {
|
|
191
|
-
if (key === "review" || key === "agent") onTabChange(key);
|
|
195
|
+
if (key === "review" || key === "agent" || key === "tree") onTabChange(key);
|
|
192
196
|
}}
|
|
193
197
|
theme={theme}
|
|
194
198
|
>
|
|
195
199
|
<TabList>
|
|
196
200
|
<Tab id="review">Review</Tab>
|
|
201
|
+
<Tab id="tree">Tree</Tab>
|
|
197
202
|
<Tab id="agent">Agent</Tab>
|
|
198
203
|
</TabList>
|
|
199
204
|
</Tabs>
|
|
200
|
-
{railTab === "
|
|
205
|
+
{railTab === "tree" ? (
|
|
206
|
+
<TreePane {...tree} theme={theme} />
|
|
207
|
+
) : railTab === "agent" ? (
|
|
201
208
|
<box style={{ flexGrow: 1, flexDirection: "column" }}>
|
|
202
209
|
<AgentLauncher
|
|
203
210
|
session={session}
|
|
@@ -223,7 +230,7 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
223
230
|
</box>
|
|
224
231
|
)}
|
|
225
232
|
{/* the confirm card keeps full width, above the footer row */}
|
|
226
|
-
{railTab
|
|
233
|
+
{railTab === "review" && submitConfirm ? (
|
|
227
234
|
<ConfirmCard {...submitConfirm} theme={theme} />
|
|
228
235
|
) : null}
|
|
229
236
|
{/* footer row: collapse chevron left-bound, the submit affordance inline
|
|
@@ -234,7 +241,7 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
234
241
|
<text fg={tokens.textDim}>{">"}</text>
|
|
235
242
|
</box>
|
|
236
243
|
) : null}
|
|
237
|
-
{railTab
|
|
244
|
+
{railTab === "review" && !submitConfirm ? (
|
|
238
245
|
session.status === "resolved" ? (
|
|
239
246
|
<text fg={tokens.green}>resolved: {session.verdict!.kind.replace("_", " ")}</text>
|
|
240
247
|
) : (
|