@cueloop/client 0.1.0-alpha.63 → 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 +7 -6
- 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 +34 -22
- package/src/App.tree.test.tsx +224 -0
- package/src/App.tsx +134 -87
- package/src/app-screens.tsx +27 -1
- package/src/app-view-model.ts +7 -109
- package/src/components/AnnotationCard.tsx +2 -2
- package/src/components/DiffSheet.scroll.test.tsx +5 -2
- package/src/components/DiffSheet.tsx +1 -6
- package/src/components/KeybindsDialog.tsx +61 -17
- package/src/components/PrototypeSheet.tsx +5 -9
- package/src/components/ReviewPanel.stories.tsx +14 -0
- package/src/components/ReviewRail.stories.tsx +14 -0
- package/src/components/ReviewRail.tsx +15 -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/components/primitives/Tabs.tsx +1 -1
- package/src/components/story-fixtures.ts +6 -2
- package/src/components/story.ts +16 -4
- package/src/components/syntax-highlight.ts +10 -2
- package/src/components/terminal-pane.test.tsx +1 -1
- package/src/components/terminal-pane.ts +7 -1
- package/src/config.ts +112 -99
- package/src/diff-hunk-curate.ts +4 -104
- package/src/editor.ts +5 -1
- package/src/ghostty-terminal.test.ts +1 -1
- package/src/herdr.ts +10 -2
- package/src/index.ts +1 -0
- package/src/intent-dispatch.test.ts +235 -55
- package/src/intent-dispatch.ts +104 -34
- package/src/key-bindings.test.ts +1 -1
- package/src/key-bindings.ts +16 -7
- package/src/keymap.test.ts +9 -11
- package/src/keymap.ts +14 -7
- package/src/kitty-image.test.ts +3 -3
- package/src/prototype-browser.ts +7 -6
- package/src/serve.ts +1 -1
- package/src/session-controller.curate.test.ts +106 -4
- package/src/session-controller.share.test.ts +220 -70
- package/src/session-controller.tree.test.ts +228 -0
- package/src/session-controller.ts +370 -94
- package/src/share.ts +117 -17
- package/src/test-support.test.ts +193 -1
- package/src/test-support.ts +69 -1
- package/src/theme-presets.ts +9 -2
- 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-diff.ts +1 -4
- package/src/view-plan.ts +35 -19
- package/src/components/PlanSheet.stories.tsx +0 -113
- package/src/components/PlanSheet.tsx +0 -422
- package/src/components/plan-sheet-run-style.ts +0 -90
|
@@ -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>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import React, { useEffect, useRef, useState } from "react";
|
|
12
12
|
import type { BoxRenderable } from "@opentui/core";
|
|
13
|
+
import * as v from "valibot";
|
|
13
14
|
import { useKeyboard, useRenderer } from "@opentui/react";
|
|
14
15
|
import type { Theme } from "../theme";
|
|
15
16
|
import { quickActionBody, type QuickAction } from "../config";
|
|
@@ -95,10 +96,7 @@ type CaptureRenderer = {
|
|
|
95
96
|
* raster and fewer bytes). Without a resolution report, fall back to a fixed
|
|
96
97
|
* width at 2x and derive the height from the cell aspect.
|
|
97
98
|
*/
|
|
98
|
-
function captureConfig(
|
|
99
|
-
region: CellRegion,
|
|
100
|
-
renderer: CaptureRenderer,
|
|
101
|
-
): { viewport: PrototypeViewport; deviceScaleFactor: number } {
|
|
99
|
+
function captureConfig(region: CellRegion, renderer: CaptureRenderer) {
|
|
102
100
|
const resolution = renderer.resolution;
|
|
103
101
|
|
|
104
102
|
if (resolution && renderer.terminalWidth >= 1 && renderer.terminalHeight >= 1) {
|
|
@@ -174,11 +172,9 @@ function PrototypeSheetImpl({
|
|
|
174
172
|
// harness), but the launch trigger still runs.
|
|
175
173
|
useEffect(() => {
|
|
176
174
|
if (!renderer) return;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
const write =
|
|
181
|
-
typeof rawWrite === "function" ? (chunk: string) => rawWrite.call(renderer, chunk) : null;
|
|
175
|
+
const rendererOutput = v.safeParse(v.object({ writeOut: v.function() }), renderer);
|
|
176
|
+
const rawWrite = rendererOutput.success ? rendererOutput.output.writeOut : null;
|
|
177
|
+
const write = rawWrite ? (chunk: string) => rawWrite.call(renderer, chunk) : null;
|
|
182
178
|
const medium = resolveTransmitMedium();
|
|
183
179
|
const paint = (): void => {
|
|
184
180
|
if (!write) return;
|
|
@@ -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,
|
|
@@ -187,15 +191,20 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
187
191
|
>
|
|
188
192
|
<Tabs
|
|
189
193
|
selectedKey={railTab}
|
|
190
|
-
onSelectionChange={(key) =>
|
|
194
|
+
onSelectionChange={(key) => {
|
|
195
|
+
if (key === "review" || key === "agent" || key === "tree") onTabChange(key);
|
|
196
|
+
}}
|
|
191
197
|
theme={theme}
|
|
192
198
|
>
|
|
193
199
|
<TabList>
|
|
194
200
|
<Tab id="review">Review</Tab>
|
|
201
|
+
<Tab id="tree">Tree</Tab>
|
|
195
202
|
<Tab id="agent">Agent</Tab>
|
|
196
203
|
</TabList>
|
|
197
204
|
</Tabs>
|
|
198
|
-
{railTab === "
|
|
205
|
+
{railTab === "tree" ? (
|
|
206
|
+
<TreePane {...tree} theme={theme} />
|
|
207
|
+
) : railTab === "agent" ? (
|
|
199
208
|
<box style={{ flexGrow: 1, flexDirection: "column" }}>
|
|
200
209
|
<AgentLauncher
|
|
201
210
|
session={session}
|
|
@@ -221,7 +230,7 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
221
230
|
</box>
|
|
222
231
|
)}
|
|
223
232
|
{/* the confirm card keeps full width, above the footer row */}
|
|
224
|
-
{railTab
|
|
233
|
+
{railTab === "review" && submitConfirm ? (
|
|
225
234
|
<ConfirmCard {...submitConfirm} theme={theme} />
|
|
226
235
|
) : null}
|
|
227
236
|
{/* footer row: collapse chevron left-bound, the submit affordance inline
|
|
@@ -232,7 +241,7 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
|
|
|
232
241
|
<text fg={tokens.textDim}>{">"}</text>
|
|
233
242
|
</box>
|
|
234
243
|
) : null}
|
|
235
|
-
{railTab
|
|
244
|
+
{railTab === "review" && !submitConfirm ? (
|
|
236
245
|
session.status === "resolved" ? (
|
|
237
246
|
<text fg={tokens.green}>resolved: {session.verdict!.kind.replace("_", " ")}</text>
|
|
238
247
|
) : (
|