@cueloop/client 0.1.0-alpha.32 → 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.diff.test.tsx +1 -1
- package/src/App.export.test.tsx +1 -1
- package/src/App.plan-review.test.tsx +6 -5
- package/src/App.readonly.test.tsx +5 -6
- package/src/App.rendering.test.tsx +2 -1
- package/src/App.review-panel.test.tsx +16 -12
- package/src/App.submit-confirm.test.tsx +2 -6
- package/src/App.test.tsx +4 -4
- package/src/App.toast.test.tsx +68 -0
- package/src/App.tsx +153 -49
- package/src/App.walk.test.tsx +1 -1
- package/src/components/AnnotationCard.stories.tsx +1 -1
- package/src/components/AnnotationCard.tsx +18 -13
- package/src/components/ConfirmCard.tsx +1 -0
- package/src/components/ConfirmDialog.tsx +1 -1
- package/src/components/DiffSheet.stories.tsx +18 -0
- package/src/components/DiffSheet.tsx +38 -12
- package/src/components/InboxList.tsx +1 -1
- 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 +1 -1
- package/src/components/PlanSheet.tsx +21 -70
- package/src/components/PromptDialog.tsx +1 -1
- package/src/components/ReviewPanel.stories.tsx +4 -16
- 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.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/__snapshots__/stories.test.tsx.snap +598 -566
- package/src/components/primitives/Button.stories.tsx +2 -2
- package/src/components/primitives/Dialog.tsx +4 -1
- package/src/components/primitives/Tabs.tsx +19 -38
- 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.ts +87 -23
- package/src/version.ts +5 -0
- package/src/components/ComposeBar.stories.tsx +0 -18
- package/src/components/ComposeBar.tsx +0 -54
|
@@ -11,7 +11,7 @@ export const CommentDraft: Story = {
|
|
|
11
11
|
render: () => (
|
|
12
12
|
<AnnotationCard kind="comment" quote="persists sessions" draft={{ text: "Which daemon?", ...draftCallbacks }} />
|
|
13
13
|
),
|
|
14
|
-
expectedColors: [DARK.accent
|
|
14
|
+
expectedColors: [DARK.accent],
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export const SuggestionDraft: Story = {
|
|
@@ -32,8 +32,10 @@ export interface AnnotationSaved {
|
|
|
32
32
|
isSelected: boolean;
|
|
33
33
|
isOrphan: boolean;
|
|
34
34
|
isBlocking: boolean;
|
|
35
|
-
/**
|
|
35
|
+
/** A collaborator's resolved display name; own notes carry none. Set = name-in-border, blue. */
|
|
36
36
|
authorLabel?: string;
|
|
37
|
+
/** The viewer's own note when collaborators are also present (e.g. "me"). Set = tag-in-border, accent. */
|
|
38
|
+
selfLabel?: string;
|
|
37
39
|
/** Non-null while the card body is being rewritten in place. */
|
|
38
40
|
editing: AnnotationDraft | null;
|
|
39
41
|
onPress: () => void;
|
|
@@ -122,15 +124,15 @@ function DraftEditor({
|
|
|
122
124
|
}}
|
|
123
125
|
style={{
|
|
124
126
|
height: rows,
|
|
125
|
-
backgroundColor:
|
|
126
|
-
focusedBackgroundColor:
|
|
127
|
+
backgroundColor: "transparent",
|
|
128
|
+
focusedBackgroundColor: "transparent",
|
|
127
129
|
textColor: tokens.text,
|
|
128
130
|
focusedTextColor: tokens.text,
|
|
129
131
|
}}
|
|
130
132
|
/>
|
|
131
133
|
<Toolbar>
|
|
132
134
|
<Button variant="solid" marginRight={2} onPress={draft.onSave} theme={theme}>
|
|
133
|
-
{" Save
|
|
135
|
+
{" Save "}
|
|
134
136
|
</Button>
|
|
135
137
|
<Button onPress={draft.onCancel} theme={theme}>
|
|
136
138
|
{" Cancel esc "}
|
|
@@ -160,6 +162,7 @@ export function AnnotationCard({ id, kind, quote, draft, saved, theme }: Annotat
|
|
|
160
162
|
title={` ${verb} "${truncateToSingleLine(quote, 40)}" `}
|
|
161
163
|
contentRows={editorRowCount + 1}
|
|
162
164
|
borderColor={kindColor}
|
|
165
|
+
backgroundColor="transparent"
|
|
163
166
|
marginLeft={2}
|
|
164
167
|
marginRight={2}
|
|
165
168
|
theme={theme}
|
|
@@ -169,24 +172,26 @@ export function AnnotationCard({ id, kind, quote, draft, saved, theme }: Annotat
|
|
|
169
172
|
);
|
|
170
173
|
}
|
|
171
174
|
const card = saved!;
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
+
const collaboratorName = card.authorLabel !== undefined && card.authorLabel !== "" ? card.authorLabel : undefined;
|
|
176
|
+
const ownTag = card.selfLabel !== undefined && card.selfLabel !== "" ? card.selfLabel : undefined;
|
|
177
|
+
const borderLabel = collaboratorName ?? ownTag;
|
|
178
|
+
const borderColor = ownTag ? tokens.accent : tokens.blue;
|
|
179
|
+
const indent = borderLabel ? "" : " ";
|
|
175
180
|
const marker = card.isSelected ? "▸ " : indent;
|
|
176
181
|
// Fixed content rows (a bordered box must declare a height or it collapses): header + quote + body, or the editor.
|
|
177
182
|
const contentRows = card.editing ? editorRowCount + 3 : 3;
|
|
178
183
|
// The border + its side padding eat this much content width, so text truncates shorter.
|
|
179
|
-
const borderInset =
|
|
184
|
+
const borderInset = borderLabel ? 4 : 0;
|
|
180
185
|
return (
|
|
181
186
|
<box
|
|
182
187
|
id={id}
|
|
183
|
-
title={
|
|
188
|
+
title={borderLabel ? ` ${borderLabel} ` : undefined}
|
|
184
189
|
style={{
|
|
185
190
|
flexDirection: "column",
|
|
186
|
-
marginBottom:
|
|
187
|
-
backgroundColor: card.isSelected ? tokens.elevated : undefined,
|
|
188
|
-
...(
|
|
189
|
-
? { height: cardHeight(contentRows), border: true, borderStyle: FRAME_BORDER_STYLE, borderColor
|
|
191
|
+
marginBottom: 0,
|
|
192
|
+
backgroundColor: card.isSelected && !card.editing ? tokens.elevated : undefined,
|
|
193
|
+
...(borderLabel
|
|
194
|
+
? { height: cardHeight(contentRows), border: true, borderStyle: FRAME_BORDER_STYLE, borderColor, paddingLeft: 1, paddingRight: 1 }
|
|
190
195
|
: {}),
|
|
191
196
|
}}
|
|
192
197
|
onMouseUp={card.onPress}
|
|
@@ -44,7 +44,7 @@ export function ConfirmDialog({
|
|
|
44
44
|
<box style={{ flexGrow: 1 }} />
|
|
45
45
|
<Toolbar>
|
|
46
46
|
<Button variant="solid" onPress={onConfirm} theme={theme}>
|
|
47
|
-
{` ${confirmLabel}
|
|
47
|
+
{` ${confirmLabel} `}
|
|
48
48
|
</Button>
|
|
49
49
|
<Button onPress={onCancel} theme={theme}>
|
|
50
50
|
{` ${cancelLabel} esc `}
|
|
@@ -18,6 +18,24 @@ export const CursorOnAddedLine: Story = {
|
|
|
18
18
|
size: { width: 90, height: 16 },
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export const ComposingOnLine: Story = {
|
|
22
|
+
render: () => (
|
|
23
|
+
<DiffSheet
|
|
24
|
+
rows={fixtureDiffRows()}
|
|
25
|
+
cursor={4}
|
|
26
|
+
annotations={[]}
|
|
27
|
+
compose={{
|
|
28
|
+
kind: "comment",
|
|
29
|
+
rowIndex: 4,
|
|
30
|
+
quote: " private items = new Map();",
|
|
31
|
+
draft: { text: "Map needs an eviction story.", onInput: () => {}, onSave: () => {}, onCancel: () => {} },
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
34
|
+
),
|
|
35
|
+
expectedColors: [DARK.accent],
|
|
36
|
+
size: { width: 90, height: 16 },
|
|
37
|
+
};
|
|
38
|
+
|
|
21
39
|
export const AnnotatedLine: Story = {
|
|
22
40
|
render: () => (
|
|
23
41
|
<DiffSheet
|
|
@@ -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);
|
|
@@ -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,7 +224,17 @@ 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") {
|
|
@@ -227,14 +253,14 @@ export function DiffSheet({ rows, cursor, annotations, focusedAnnotationId, them
|
|
|
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>
|
|
@@ -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: () => (
|
|
@@ -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,
|
|
@@ -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 {
|
|
@@ -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
|
);
|
|
@@ -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 },
|