@cueloop/client 0.1.0-alpha.31 → 0.1.0-alpha.33
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 +4 -3
- package/src/App.diff.test.tsx +5 -2
- package/src/App.export.test.tsx +5 -7
- package/src/App.plan-review.test.tsx +14 -17
- package/src/App.readonly.test.tsx +9 -7
- package/src/App.rendering.test.tsx +6 -2
- package/src/App.review-panel.test.tsx +19 -14
- package/src/App.submit-confirm.test.tsx +6 -7
- package/src/App.test.tsx +8 -5
- package/src/App.toast.test.tsx +68 -0
- package/src/App.tsx +154 -50
- package/src/App.walk.test.tsx +7 -4
- package/src/components/AnnotationCard.stories.tsx +1 -1
- package/src/components/AnnotationCard.tsx +18 -13
- package/src/components/CompletionOverlay.tsx +1 -1
- package/src/components/ConfirmCard.tsx +1 -0
- package/src/components/ConfirmDialog.stories.tsx +1 -1
- package/src/components/ConfirmDialog.tsx +1 -1
- package/src/components/DiffSheet.stories.tsx +21 -3
- package/src/components/DiffSheet.tsx +45 -19
- package/src/components/InboxList.stories.tsx +1 -1
- package/src/components/InboxList.tsx +3 -3
- package/src/components/KeybindsDialog.stories.tsx +31 -0
- package/src/components/KeybindsDialog.tsx +42 -0
- package/src/components/MenuBar.stories.tsx +33 -0
- package/src/components/MenuBar.tsx +58 -0
- package/src/components/PlanSheet.stories.tsx +2 -2
- package/src/components/PlanSheet.tsx +26 -75
- package/src/components/PromptDialog.stories.tsx +2 -2
- package/src/components/PromptDialog.tsx +1 -1
- package/src/components/ReviewPanel.stories.tsx +5 -17
- package/src/components/ReviewPanel.tsx +10 -29
- package/src/components/ReviewRail.stories.tsx +39 -0
- package/src/components/ReviewRail.tsx +62 -57
- package/src/components/SettingsDialog.stories.tsx +1 -1
- package/src/components/SettingsDialog.tsx +4 -3
- package/src/components/SettingsRows.stories.tsx +1 -1
- package/src/components/SettingsRows.tsx +1 -1
- package/src/components/Toast.stories.tsx +12 -0
- package/src/components/Toast.tsx +29 -0
- package/src/components/WalkWizard.stories.tsx +1 -1
- package/src/components/WalkWizard.tsx +1 -1
- package/src/components/__snapshots__/stories.test.tsx.snap +598 -566
- package/src/components/primitives/Button.stories.tsx +2 -2
- package/src/components/primitives/Dialog.stories.tsx +1 -1
- package/src/components/primitives/Dialog.tsx +7 -5
- package/src/components/primitives/Tabs.tsx +19 -38
- package/src/components/stories-app.tsx +1 -1
- package/src/components/stories.test.tsx +2 -0
- package/src/key-bindings.test.ts +4 -4
- package/src/key-bindings.ts +41 -2
- package/src/serve.test.ts +2 -2
- package/src/session-controller.share.test.ts +13 -0
- package/src/session-controller.ts +19 -1
- package/src/test-support.test.ts +55 -0
- package/src/test-support.ts +113 -23
- package/src/theme.ts +16 -16
- package/src/version.ts +5 -0
- package/src/components/ComposeBar.stories.tsx +0 -18
- package/src/components/ComposeBar.tsx +0 -54
|
@@ -14,12 +14,22 @@ import type { DiffRow } from "../view-diff";
|
|
|
14
14
|
import type { Theme } from "../theme";
|
|
15
15
|
import { useComponentTheme } from "./theme-context";
|
|
16
16
|
import { truncateToSingleLine } from "./truncate-text";
|
|
17
|
+
import { AnnotationCard, type AnnotationDraft } from "./AnnotationCard";
|
|
18
|
+
import { FRAME_BORDER_STYLE } from "./primitives/frame";
|
|
19
|
+
|
|
20
|
+
export interface DiffComposeState {
|
|
21
|
+
kind: "comment" | "suggestion";
|
|
22
|
+
rowIndex: number;
|
|
23
|
+
quote: string;
|
|
24
|
+
draft: AnnotationDraft;
|
|
25
|
+
}
|
|
17
26
|
|
|
18
27
|
export interface DiffSheetProps {
|
|
19
28
|
rows: DiffRow[];
|
|
20
29
|
cursor: number;
|
|
21
30
|
annotations: Annotation[];
|
|
22
31
|
focusedAnnotationId?: string;
|
|
32
|
+
compose?: DiffComposeState | null;
|
|
23
33
|
theme?: Theme;
|
|
24
34
|
}
|
|
25
35
|
|
|
@@ -32,8 +42,8 @@ function rowLine(row: DiffRow): string {
|
|
|
32
42
|
return row.text.replace(/\n$/, "");
|
|
33
43
|
}
|
|
34
44
|
|
|
35
|
-
/** Chunks split after an annotated row so
|
|
36
|
-
function segmentRows(rows: DiffRow[], annotatedByRow: Map<number, Annotation
|
|
45
|
+
/** Chunks split after an annotated or composed row so a card can sit below. */
|
|
46
|
+
function segmentRows(rows: DiffRow[], annotatedByRow: Map<number, Annotation>, composeRowIndex?: number): DiffSegment[] {
|
|
37
47
|
const segments: DiffSegment[] = [];
|
|
38
48
|
let chunk: DiffRow[] = [];
|
|
39
49
|
let chunkStart = 0;
|
|
@@ -55,6 +65,9 @@ function segmentRows(rows: DiffRow[], annotatedByRow: Map<number, Annotation>):
|
|
|
55
65
|
if (annotation) {
|
|
56
66
|
closeChunk(annotation);
|
|
57
67
|
chunkStart = rowIndex + 1;
|
|
68
|
+
} else if (rowIndex === composeRowIndex) {
|
|
69
|
+
closeChunk(null);
|
|
70
|
+
chunkStart = rowIndex + 1;
|
|
58
71
|
}
|
|
59
72
|
}
|
|
60
73
|
closeChunk(null);
|
|
@@ -100,10 +113,10 @@ function DiffChunk({
|
|
|
100
113
|
// the renderable's config parser requires the gutter key to be present
|
|
101
114
|
const colors = new Map<number, { gutter: string | undefined; content: string }>();
|
|
102
115
|
if (segment.annotation) {
|
|
103
|
-
colors.set(segment.rows.length - 1, { gutter: undefined, content: tokens.
|
|
116
|
+
colors.set(segment.rows.length - 1, { gutter: undefined, content: tokens.markCommentBackground });
|
|
104
117
|
}
|
|
105
118
|
if (cursorInChunk >= 0 && cursorInChunk < segment.rows.length) {
|
|
106
|
-
colors.set(cursorInChunk, { gutter: tokens.
|
|
119
|
+
colors.set(cursorInChunk, { gutter: tokens.cursorBackground, content: tokens.cursorBackground });
|
|
107
120
|
}
|
|
108
121
|
return colors;
|
|
109
122
|
}, [segment, cursorInChunk, tokens]);
|
|
@@ -124,7 +137,7 @@ function DiffChunk({
|
|
|
124
137
|
<line-number
|
|
125
138
|
ref={gutterRef}
|
|
126
139
|
fg={tokens.textDim}
|
|
127
|
-
bg={tokens.
|
|
140
|
+
bg={tokens.background}
|
|
128
141
|
minWidth={4}
|
|
129
142
|
paddingRight={1}
|
|
130
143
|
lineNumbers={lineNumbers}
|
|
@@ -133,14 +146,14 @@ function DiffChunk({
|
|
|
133
146
|
<text style={{ wrapMode: "none" }} selectable={false}>
|
|
134
147
|
{segment.rows.map((row, lineIndex) => {
|
|
135
148
|
const sign = row.kind === "add" ? "+" : row.kind === "del" ? "-" : " ";
|
|
136
|
-
const foreground = row.kind === "add" ? tokens.
|
|
149
|
+
const foreground = row.kind === "add" ? tokens.insertedForeground : row.kind === "del" ? tokens.deletedForeground : tokens.textMuted;
|
|
137
150
|
const isCursorRow = lineIndex === cursorInChunk;
|
|
138
151
|
const isAnnotatedRow = segment.annotation !== null && lineIndex === segment.rows.length - 1;
|
|
139
152
|
return (
|
|
140
153
|
<span
|
|
141
154
|
key={lineIndex}
|
|
142
155
|
fg={foreground}
|
|
143
|
-
bg={isCursorRow ? tokens.
|
|
156
|
+
bg={isCursorRow ? tokens.cursorBackground : isAnnotatedRow ? tokens.markCommentBackground : undefined}
|
|
144
157
|
>
|
|
145
158
|
{(lineIndex > 0 ? "\n" : "") + sign + rowLine(row)}
|
|
146
159
|
</span>
|
|
@@ -160,7 +173,7 @@ function DiffChunk({
|
|
|
160
173
|
);
|
|
161
174
|
}
|
|
162
175
|
|
|
163
|
-
export function DiffSheet({ rows, cursor, annotations, focusedAnnotationId, theme }: DiffSheetProps): React.ReactNode {
|
|
176
|
+
export function DiffSheet({ rows, cursor, annotations, focusedAnnotationId, compose, theme }: DiffSheetProps): React.ReactNode {
|
|
164
177
|
const tokens = useComponentTheme(theme);
|
|
165
178
|
const scrollRef = useRef<ScrollBoxRenderable | null>(null);
|
|
166
179
|
|
|
@@ -175,7 +188,10 @@ export function DiffSheet({ rows, cursor, annotations, focusedAnnotationId, them
|
|
|
175
188
|
return byRow;
|
|
176
189
|
}, [rows, annotations]);
|
|
177
190
|
|
|
178
|
-
const segments = useMemo(
|
|
191
|
+
const segments = useMemo(
|
|
192
|
+
() => segmentRows(rows, annotatedByRow, compose?.rowIndex),
|
|
193
|
+
[rows, annotatedByRow, compose?.rowIndex],
|
|
194
|
+
);
|
|
179
195
|
|
|
180
196
|
// content y offset per row index, for cursor-following scroll
|
|
181
197
|
const rowOffsets = useMemo(() => {
|
|
@@ -208,33 +224,43 @@ export function DiffSheet({ rows, cursor, annotations, focusedAnnotationId, them
|
|
|
208
224
|
}, [cursor, rowOffsets]);
|
|
209
225
|
|
|
210
226
|
return (
|
|
211
|
-
<box
|
|
227
|
+
<box
|
|
228
|
+
style={{
|
|
229
|
+
flexGrow: 1,
|
|
230
|
+
flexDirection: "column",
|
|
231
|
+
border: true,
|
|
232
|
+
borderStyle: FRAME_BORDER_STYLE,
|
|
233
|
+
borderColor: tokens.text,
|
|
234
|
+
paddingLeft: 1,
|
|
235
|
+
paddingTop: 0,
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
212
238
|
<scrollbox ref={scrollRef} style={{ flexGrow: 1 }} focused={false}>
|
|
213
239
|
{segments.map((segment, segmentIndex) => {
|
|
214
240
|
if (segment.kind === "header") {
|
|
215
241
|
const isCursor = segment.rowIndex === cursor;
|
|
216
242
|
if (segment.row.kind === "file") {
|
|
217
243
|
return (
|
|
218
|
-
<text key={segmentIndex} fg={tokens.text} bg={isCursor ? tokens.
|
|
244
|
+
<text key={segmentIndex} fg={tokens.text} bg={isCursor ? tokens.cursorBackground : tokens.panel}>
|
|
219
245
|
{isCursor ? "▎" : " "}■ {rowLine(segment.row)}
|
|
220
246
|
</text>
|
|
221
247
|
);
|
|
222
248
|
}
|
|
223
249
|
return (
|
|
224
|
-
<text key={segmentIndex} fg={tokens.blue} bg={isCursor ? tokens.
|
|
250
|
+
<text key={segmentIndex} fg={tokens.blue} bg={isCursor ? tokens.cursorBackground : undefined}>
|
|
225
251
|
{isCursor ? "▎" : " "}
|
|
226
252
|
{rowLine(segment.row)}
|
|
227
253
|
</text>
|
|
228
254
|
);
|
|
229
255
|
}
|
|
256
|
+
const lastRowIndex = segment.firstRowIndex + segment.rows.length - 1;
|
|
230
257
|
return (
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
/>
|
|
258
|
+
<React.Fragment key={segmentIndex}>
|
|
259
|
+
<DiffChunk segment={segment} cursor={cursor} focusedAnnotationId={focusedAnnotationId} theme={theme} />
|
|
260
|
+
{compose && compose.rowIndex === lastRowIndex ? (
|
|
261
|
+
<AnnotationCard kind={compose.kind} quote={compose.quote} draft={compose.draft} theme={theme} />
|
|
262
|
+
) : null}
|
|
263
|
+
</React.Fragment>
|
|
238
264
|
);
|
|
239
265
|
})}
|
|
240
266
|
</scrollbox>
|
|
@@ -8,7 +8,7 @@ export const meta: StoryMeta = { title: "InboxList" };
|
|
|
8
8
|
|
|
9
9
|
export const TwoPending: Story = {
|
|
10
10
|
render: () => <InboxList inbox={[fixturePlanSession(), fixtureDiffSession()]} cursor={0} />,
|
|
11
|
-
expectedColors: [DARK.accent, DARK.
|
|
11
|
+
expectedColors: [DARK.accent, DARK.cursorBackground],
|
|
12
12
|
size: { width: 80, height: 12 },
|
|
13
13
|
};
|
|
14
14
|
|
|
@@ -23,7 +23,7 @@ export interface InboxListProps {
|
|
|
23
23
|
export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListProps): React.ReactNode {
|
|
24
24
|
const tokens = useComponentTheme(theme);
|
|
25
25
|
return (
|
|
26
|
-
<box style={{ flexDirection: "column", width: "100%", height: "100%", backgroundColor: tokens.
|
|
26
|
+
<box style={{ flexDirection: "column", width: "100%", height: "100%", backgroundColor: tokens.background, padding: 1 }}>
|
|
27
27
|
<text fg={tokens.accent}>cueloop · inbox ({inbox.length} pending)</text>
|
|
28
28
|
<text> </text>
|
|
29
29
|
{inbox.length === 0 ? (
|
|
@@ -33,7 +33,7 @@ export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListPr
|
|
|
33
33
|
const selected = index === cursor;
|
|
34
34
|
const title = session.artifact.meta.title ?? session.id;
|
|
35
35
|
return (
|
|
36
|
-
<box key={session.id} style={{ flexDirection: "row", backgroundColor: selected ? tokens.
|
|
36
|
+
<box key={session.id} style={{ flexDirection: "row", backgroundColor: selected ? tokens.cursorBackground : undefined }}>
|
|
37
37
|
<text fg={selected ? tokens.text : tokens.textMuted}>
|
|
38
38
|
{selected ? "▸ " : " "}
|
|
39
39
|
{title} · {session.workspace.branch} · {session.artifact.type}
|
|
@@ -49,7 +49,7 @@ export function InboxList({ inbox, cursor, onRequestDelete, theme }: InboxListPr
|
|
|
49
49
|
})
|
|
50
50
|
)}
|
|
51
51
|
<box style={{ flexGrow: 1 }} />
|
|
52
|
-
<StatusBar theme={theme}>j/k move ·
|
|
52
|
+
<StatusBar theme={theme}>j/k move · enter open · d delete · q quit</StatusBar>
|
|
53
53
|
</box>
|
|
54
54
|
);
|
|
55
55
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { KeybindsDialog } from "./KeybindsDialog";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "KeybindsDialog" };
|
|
7
|
+
|
|
8
|
+
const SECTIONS = [
|
|
9
|
+
{
|
|
10
|
+
title: "Review",
|
|
11
|
+
entries: [
|
|
12
|
+
{ keys: "j / k", label: "move" },
|
|
13
|
+
{ keys: "v", label: "span" },
|
|
14
|
+
{ keys: "c", label: "comment" },
|
|
15
|
+
{ keys: "enter", label: "submit" },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
title: "Submit",
|
|
20
|
+
entries: [
|
|
21
|
+
{ keys: "← / →", label: "verdict" },
|
|
22
|
+
{ keys: "esc", label: "cancel" },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const Cheatsheet: Story = {
|
|
28
|
+
render: () => <KeybindsDialog sections={SECTIONS} />,
|
|
29
|
+
expectedColors: [DARK.accent],
|
|
30
|
+
size: { width: 70, height: 24 },
|
|
31
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import type { Theme } from "../theme";
|
|
4
|
+
import { useComponentTheme } from "./theme-context";
|
|
5
|
+
import { Dialog } from "./primitives/Dialog";
|
|
6
|
+
import type { CheatsheetSection } from "../key-bindings";
|
|
7
|
+
|
|
8
|
+
export interface KeybindsDialogProps {
|
|
9
|
+
sections: CheatsheetSection[];
|
|
10
|
+
theme?: Theme;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function KeybindsDialog({ sections, theme }: KeybindsDialogProps): React.ReactNode {
|
|
14
|
+
const tokens = useComponentTheme(theme);
|
|
15
|
+
const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
|
|
16
|
+
const contentRows = sections.reduce((sum, section) => sum + 1 + section.entries.length + 1, 0) + 1;
|
|
17
|
+
const width = Math.min(52, terminalWidth - 6);
|
|
18
|
+
const height = Math.min(terminalHeight - 4, contentRows + 3);
|
|
19
|
+
return (
|
|
20
|
+
<Dialog isOpen title=" keybinds " width={width} height={height} background={tokens.elevated} theme={theme}>
|
|
21
|
+
<box style={{ flexDirection: "column", flexGrow: 1, paddingLeft: 1, paddingRight: 1, paddingTop: 1 }}>
|
|
22
|
+
{sections.map((section) => (
|
|
23
|
+
<box key={section.title} style={{ flexDirection: "column" }}>
|
|
24
|
+
<text fg={tokens.accent}>{section.title}</text>
|
|
25
|
+
{section.entries.map((entry, index) => (
|
|
26
|
+
<text key={index}>
|
|
27
|
+
<span fg={tokens.text}>{entry.keys.padEnd(12)}</span>
|
|
28
|
+
<span fg={tokens.textMuted}>{entry.label}</span>
|
|
29
|
+
</text>
|
|
30
|
+
))}
|
|
31
|
+
<text> </text>
|
|
32
|
+
</box>
|
|
33
|
+
))}
|
|
34
|
+
<box style={{ flexGrow: 1 }} />
|
|
35
|
+
<box style={{ flexDirection: "row" }}>
|
|
36
|
+
<box style={{ flexGrow: 1 }} />
|
|
37
|
+
<text fg={tokens.textDim}>[esc]</text>
|
|
38
|
+
</box>
|
|
39
|
+
</box>
|
|
40
|
+
</Dialog>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DARK } from "../theme";
|
|
3
|
+
import type { Story, StoryMeta } from "./story";
|
|
4
|
+
import { MenuBar } from "./MenuBar";
|
|
5
|
+
|
|
6
|
+
export const meta: StoryMeta = { title: "MenuBar" };
|
|
7
|
+
|
|
8
|
+
const callbacks = { onToggle: () => {}, onSettings: () => {}, onKeybinds: () => {} };
|
|
9
|
+
|
|
10
|
+
function BottomFrame({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
11
|
+
return (
|
|
12
|
+
<box style={{ flexDirection: "column", width: "100%", height: "100%", justifyContent: "flex-end" }}>{children}</box>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Closed: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<BottomFrame>
|
|
19
|
+
<MenuBar open={false} version="0.1.0-alpha.32" {...callbacks} />
|
|
20
|
+
</BottomFrame>
|
|
21
|
+
),
|
|
22
|
+
size: { width: 60, height: 8 },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Open: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<BottomFrame>
|
|
28
|
+
<MenuBar open version="0.1.0-alpha.32" {...callbacks} />
|
|
29
|
+
</BottomFrame>
|
|
30
|
+
),
|
|
31
|
+
expectedColors: [DARK.text],
|
|
32
|
+
size: { width: 60, height: 8 },
|
|
33
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import type { Theme } from "../theme";
|
|
4
|
+
import { useComponentTheme } from "./theme-context";
|
|
5
|
+
import { FRAME_BORDER_STYLE } from "./primitives/frame";
|
|
6
|
+
|
|
7
|
+
export interface MenuBarProps {
|
|
8
|
+
open: boolean;
|
|
9
|
+
version: string;
|
|
10
|
+
/** The transient status line, shown between the menu and the version. */
|
|
11
|
+
status?: string;
|
|
12
|
+
onToggle: () => void;
|
|
13
|
+
onSettings: () => void;
|
|
14
|
+
onKeybinds: () => void;
|
|
15
|
+
theme?: Theme;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DROPUP_OPTION_ROWS = 2;
|
|
19
|
+
|
|
20
|
+
export function MenuBar({ open, version, status, onToggle, onSettings, onKeybinds, theme }: MenuBarProps): React.ReactNode {
|
|
21
|
+
const tokens = useComponentTheme(theme);
|
|
22
|
+
const { height: terminalHeight } = useTerminalDimensions();
|
|
23
|
+
const dropupHeight = DROPUP_OPTION_ROWS + 2;
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
{open ? (
|
|
27
|
+
<box
|
|
28
|
+
style={{
|
|
29
|
+
position: "absolute",
|
|
30
|
+
left: 1,
|
|
31
|
+
top: Math.max(0, terminalHeight - 1 - dropupHeight),
|
|
32
|
+
width: 16,
|
|
33
|
+
height: dropupHeight,
|
|
34
|
+
border: true,
|
|
35
|
+
borderStyle: FRAME_BORDER_STYLE,
|
|
36
|
+
borderColor: tokens.text,
|
|
37
|
+
flexDirection: "column",
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<box onMouseUp={onSettings}>
|
|
41
|
+
<text fg={tokens.text}> Settings</text>
|
|
42
|
+
</box>
|
|
43
|
+
<box onMouseUp={onKeybinds}>
|
|
44
|
+
<text fg={tokens.text}> Keybinds</text>
|
|
45
|
+
</box>
|
|
46
|
+
</box>
|
|
47
|
+
) : null}
|
|
48
|
+
<box style={{ flexDirection: "row", height: 1, backgroundColor: tokens.panel, paddingLeft: 1, paddingRight: 1 }}>
|
|
49
|
+
<box onMouseUp={onToggle}>
|
|
50
|
+
<text fg={tokens.textDim}>menu</text>
|
|
51
|
+
</box>
|
|
52
|
+
{status ? <text fg={tokens.accent}>{` ${status}`}</text> : null}
|
|
53
|
+
<box style={{ flexGrow: 1 }} />
|
|
54
|
+
<text fg={tokens.textDim}>v{version}</text>
|
|
55
|
+
</box>
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -6,7 +6,7 @@ import { fixtureDisplay, fixtureMarks, fixturePlanSession } from "./story-fixtur
|
|
|
6
6
|
|
|
7
7
|
export const meta: StoryMeta = { title: "PlanSheet" };
|
|
8
8
|
|
|
9
|
-
const callbacks = { onLineActivate: () => {}
|
|
9
|
+
const callbacks = { onLineActivate: () => {} };
|
|
10
10
|
|
|
11
11
|
export const AnnotatedPlan: Story = {
|
|
12
12
|
render: () => (
|
|
@@ -21,7 +21,7 @@ export const AnnotatedPlan: Story = {
|
|
|
21
21
|
{...callbacks}
|
|
22
22
|
/>
|
|
23
23
|
),
|
|
24
|
-
expectedColors: [DARK.
|
|
24
|
+
expectedColors: [DARK.markCommentBackground, DARK.markSuggestionBackground, DARK.cursorBackground],
|
|
25
25
|
size: { width: 90, height: 28 },
|
|
26
26
|
};
|
|
27
27
|
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
displayText,
|
|
18
18
|
overlayMarks,
|
|
19
19
|
renderedOffsetFor,
|
|
20
|
-
revisionDelta,
|
|
21
20
|
workRangeForRendered,
|
|
22
21
|
type DisplayBlock,
|
|
23
22
|
type Mark,
|
|
@@ -28,8 +27,7 @@ import type { Theme } from "../theme";
|
|
|
28
27
|
import { useComponentTheme } from "./theme-context";
|
|
29
28
|
import { CodeBlock } from "./CodeBlock";
|
|
30
29
|
import { AnnotationCard, type AnnotationDraft } from "./AnnotationCard";
|
|
31
|
-
import {
|
|
32
|
-
import { Toolbar } from "./primitives/Toolbar";
|
|
30
|
+
import { FRAME_BORDER_STYLE } from "./primitives/frame";
|
|
33
31
|
|
|
34
32
|
export interface PlanSelection {
|
|
35
33
|
displayIndex: number;
|
|
@@ -63,10 +61,6 @@ export interface PlanSheetProps {
|
|
|
63
61
|
compose: PlanComposeState | null;
|
|
64
62
|
editOrphanCount: number;
|
|
65
63
|
onLineActivate: (displayIndex: number) => void;
|
|
66
|
-
onEditRequest: () => void;
|
|
67
|
-
onShareRequest: () => void;
|
|
68
|
-
/** Owner-only: show the Edit and Share affordances. A share viewer never sees either. */
|
|
69
|
-
canEdit: boolean;
|
|
70
64
|
theme?: Theme;
|
|
71
65
|
}
|
|
72
66
|
|
|
@@ -99,9 +93,6 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
|
|
|
99
93
|
compose,
|
|
100
94
|
editOrphanCount,
|
|
101
95
|
onLineActivate,
|
|
102
|
-
onEditRequest,
|
|
103
|
-
onShareRequest,
|
|
104
|
-
canEdit,
|
|
105
96
|
theme,
|
|
106
97
|
}: PlanSheetProps,
|
|
107
98
|
handleRef,
|
|
@@ -193,7 +184,7 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
|
|
|
193
184
|
<span fg={tokens.textDim}>{marker(block)}</span>
|
|
194
185
|
</text>
|
|
195
186
|
<text
|
|
196
|
-
bg={isCursor ? tokens.
|
|
187
|
+
bg={isCursor ? tokens.cursorBackground : undefined}
|
|
197
188
|
selectable
|
|
198
189
|
selectionBg={tokens.accent}
|
|
199
190
|
selectionFg={tokens.accentInk}
|
|
@@ -220,69 +211,29 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
|
|
|
220
211
|
|
|
221
212
|
return (
|
|
222
213
|
<box style={{ flexGrow: 1, flexDirection: "column" }}>
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
{children}
|
|
239
|
-
</scrollbox>
|
|
240
|
-
</box>
|
|
241
|
-
);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
/** Sheet chrome: submitted-by + revision delta left, the Edit and Share word-buttons right. */
|
|
245
|
-
function SheetHeader({
|
|
246
|
-
session,
|
|
247
|
-
onEditRequest,
|
|
248
|
-
onShareRequest,
|
|
249
|
-
canEdit,
|
|
250
|
-
theme,
|
|
251
|
-
}: {
|
|
252
|
-
session: ReviewSession;
|
|
253
|
-
onEditRequest: () => void;
|
|
254
|
-
onShareRequest: () => void;
|
|
255
|
-
canEdit: boolean;
|
|
256
|
-
theme?: Theme;
|
|
257
|
-
}): React.ReactNode {
|
|
258
|
-
const tokens = useComponentTheme(theme);
|
|
259
|
-
const revisionCount = session.revisions.length;
|
|
260
|
-
const previous = revisionCount > 1 ? session.revisions[revisionCount - 2] : undefined;
|
|
261
|
-
const delta = previous ? revisionDelta(previous.content, session.artifact.content) : null;
|
|
262
|
-
return (
|
|
263
|
-
<box style={{ height: 1, flexDirection: "row", paddingLeft: 2, paddingRight: 1 }}>
|
|
264
|
-
<text fg={tokens.textDim}>
|
|
265
|
-
submitted by <span fg={tokens.textMuted}>{session.artifact.meta.agent ?? "unknown"}</span> · revision {revisionCount}
|
|
266
|
-
{delta ? (
|
|
267
|
-
<span fg={tokens.green}>
|
|
268
|
-
{" "}· v{revisionCount - 1}→v{revisionCount} +{delta.added} -{delta.removed}
|
|
269
|
-
</span>
|
|
214
|
+
<box
|
|
215
|
+
style={{
|
|
216
|
+
flexGrow: 1,
|
|
217
|
+
flexDirection: "column",
|
|
218
|
+
border: true,
|
|
219
|
+
borderStyle: FRAME_BORDER_STYLE,
|
|
220
|
+
borderColor: tokens.text,
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
223
|
+
{editOrphanCount > 0 ? (
|
|
224
|
+
<box style={{ height: 1, backgroundColor: tokens.markCommentBackground, paddingLeft: 1 }}>
|
|
225
|
+
<text fg={tokens.red}>
|
|
226
|
+
{editOrphanCount} annotation{editOrphanCount === 1 ? "" : "s"} no longer match - the passage was removed.
|
|
227
|
+
</text>
|
|
228
|
+
</box>
|
|
270
229
|
) : null}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
<Button onPress={onEditRequest} theme={theme}>
|
|
276
|
-
{" Edit "}
|
|
277
|
-
</Button>
|
|
278
|
-
<Button onPress={onShareRequest} theme={theme}>
|
|
279
|
-
{" Share "}
|
|
280
|
-
</Button>
|
|
281
|
-
</Toolbar>
|
|
282
|
-
) : null}
|
|
230
|
+
<scrollbox ref={scrollRef} style={{ flexGrow: 1, paddingLeft: 1, paddingTop: 0 }} focused={false}>
|
|
231
|
+
{children}
|
|
232
|
+
</scrollbox>
|
|
233
|
+
</box>
|
|
283
234
|
</box>
|
|
284
235
|
);
|
|
285
|
-
}
|
|
236
|
+
});
|
|
286
237
|
|
|
287
238
|
/** Vertical rhythm: gaps live ABOVE blocks so boundaries never collapse. */
|
|
288
239
|
function topGap(previous: DisplayBlock | undefined, current: DisplayBlock): number {
|
|
@@ -312,13 +263,13 @@ function runStyle(run: StyleRun, block: DisplayBlock, tokens: Theme): { fg?: str
|
|
|
312
263
|
const struck = block.type === "del";
|
|
313
264
|
switch (run.role) {
|
|
314
265
|
case "ins":
|
|
315
|
-
return { fg: tokens.
|
|
266
|
+
return { fg: tokens.insertedForeground };
|
|
316
267
|
case "del":
|
|
317
|
-
return { fg: tokens.
|
|
268
|
+
return { fg: tokens.deletedForeground };
|
|
318
269
|
case "mark-comment":
|
|
319
|
-
return { fg: tokens.text, bg: tokens.
|
|
270
|
+
return { fg: tokens.text, bg: tokens.markCommentBackground };
|
|
320
271
|
case "mark-suggestion":
|
|
321
|
-
return { fg: tokens.text, bg: tokens.
|
|
272
|
+
return { fg: tokens.text, bg: tokens.markSuggestionBackground };
|
|
322
273
|
case "mark-focus":
|
|
323
274
|
return { fg: tokens.accentInk, bg: tokens.accent };
|
|
324
275
|
case "kspan":
|
|
@@ -16,7 +16,7 @@ export const RenameAuthor: Story = {
|
|
|
16
16
|
onInput={() => {}}
|
|
17
17
|
/>
|
|
18
18
|
),
|
|
19
|
-
expectedColors: [DARK.
|
|
19
|
+
expectedColors: [DARK.elevated, DARK.accent],
|
|
20
20
|
size: { width: 64, height: 16 },
|
|
21
21
|
};
|
|
22
22
|
|
|
@@ -31,6 +31,6 @@ export const NameYourself: Story = {
|
|
|
31
31
|
onInput={() => {}}
|
|
32
32
|
/>
|
|
33
33
|
),
|
|
34
|
-
expectedColors: [DARK.
|
|
34
|
+
expectedColors: [DARK.elevated],
|
|
35
35
|
size: { width: 64, height: 16 },
|
|
36
36
|
};
|
|
@@ -55,7 +55,7 @@ export function PromptDialog({ isOpen, title, label, value, placeholder, onInput
|
|
|
55
55
|
}}
|
|
56
56
|
/>
|
|
57
57
|
<box style={{ flexGrow: 1 }} />
|
|
58
|
-
<text fg={tokens.textDim}
|
|
58
|
+
<text fg={tokens.textDim}>enter save · esc cancel</text>
|
|
59
59
|
</box>
|
|
60
60
|
</Dialog>
|
|
61
61
|
);
|
|
@@ -27,7 +27,7 @@ const panelCallbacks = { onDividerGrab: () => {}, onToggle: () => {} };
|
|
|
27
27
|
/** The plan area to the left of the divider, so the layout reads in context. */
|
|
28
28
|
function PanelFrame({ children }: { children: React.ReactNode }): React.ReactNode {
|
|
29
29
|
return (
|
|
30
|
-
<box style={{ flexDirection: "row", width: "100%", height: "100%", backgroundColor: DARK.
|
|
30
|
+
<box style={{ flexDirection: "row", width: "100%", height: "100%", backgroundColor: DARK.background }}>
|
|
31
31
|
<box style={{ flexGrow: 1, paddingLeft: 1 }}>
|
|
32
32
|
<text fg={DARK.textMuted}>The plan takes the width the panel hands back.</text>
|
|
33
33
|
</box>
|
|
@@ -39,38 +39,26 @@ function PanelFrame({ children }: { children: React.ReactNode }): React.ReactNod
|
|
|
39
39
|
export const ExpandedNarrow: Story = {
|
|
40
40
|
render: () => (
|
|
41
41
|
<PanelFrame>
|
|
42
|
-
<ReviewPanel mode="expanded" width={REVIEW_MIN_WIDTH}
|
|
42
|
+
<ReviewPanel mode="expanded" width={REVIEW_MIN_WIDTH} rail={rail} {...panelCallbacks} />
|
|
43
43
|
</PanelFrame>
|
|
44
44
|
),
|
|
45
|
-
expectedColors: [DARK.border],
|
|
46
45
|
size: { width: 80, height: 24 },
|
|
47
46
|
};
|
|
48
47
|
|
|
49
48
|
export const ExpandedWide: Story = {
|
|
50
49
|
render: () => (
|
|
51
50
|
<PanelFrame>
|
|
52
|
-
<ReviewPanel mode="expanded" width={REVIEW_MAX_WIDTH}
|
|
51
|
+
<ReviewPanel mode="expanded" width={REVIEW_MAX_WIDTH} rail={rail} {...panelCallbacks} />
|
|
53
52
|
</PanelFrame>
|
|
54
53
|
),
|
|
55
54
|
expectedColors: [DARK.green],
|
|
56
55
|
size: { width: 90, height: 24 },
|
|
57
56
|
};
|
|
58
57
|
|
|
59
|
-
/** Dragging accents the divider glyph column. */
|
|
60
|
-
export const DraggingDivider: Story = {
|
|
61
|
-
render: () => (
|
|
62
|
-
<PanelFrame>
|
|
63
|
-
<ReviewPanel mode="expanded" width={34} height={24} dragging={true} rail={rail} {...panelCallbacks} />
|
|
64
|
-
</PanelFrame>
|
|
65
|
-
),
|
|
66
|
-
expectedColors: [DARK.accent],
|
|
67
|
-
size: { width: 80, height: 24 },
|
|
68
|
-
};
|
|
69
|
-
|
|
70
58
|
export const Compact: Story = {
|
|
71
59
|
render: () => (
|
|
72
60
|
<PanelFrame>
|
|
73
|
-
<ReviewPanel mode="compact" width={34}
|
|
61
|
+
<ReviewPanel mode="compact" width={34} rail={rail} {...panelCallbacks} />
|
|
74
62
|
</PanelFrame>
|
|
75
63
|
),
|
|
76
64
|
expectedColors: [DARK.accent, DARK.green],
|
|
@@ -80,7 +68,7 @@ export const Compact: Story = {
|
|
|
80
68
|
export const Hidden: Story = {
|
|
81
69
|
render: () => (
|
|
82
70
|
<PanelFrame>
|
|
83
|
-
<ReviewPanel mode="hidden" width={34}
|
|
71
|
+
<ReviewPanel mode="hidden" width={34} rail={rail} {...panelCallbacks} />
|
|
84
72
|
</PanelFrame>
|
|
85
73
|
),
|
|
86
74
|
size: { width: 60, height: 24 },
|