@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.
Files changed (48) hide show
  1. package/package.json +5 -5
  2. package/src/App.diff.test.tsx +1 -1
  3. package/src/App.export.test.tsx +1 -1
  4. package/src/App.plan-review.test.tsx +6 -5
  5. package/src/App.readonly.test.tsx +5 -6
  6. package/src/App.rendering.test.tsx +2 -1
  7. package/src/App.review-panel.test.tsx +16 -12
  8. package/src/App.submit-confirm.test.tsx +2 -6
  9. package/src/App.test.tsx +4 -4
  10. package/src/App.toast.test.tsx +68 -0
  11. package/src/App.tsx +153 -49
  12. package/src/App.walk.test.tsx +1 -1
  13. package/src/components/AnnotationCard.stories.tsx +1 -1
  14. package/src/components/AnnotationCard.tsx +18 -13
  15. package/src/components/ConfirmCard.tsx +1 -0
  16. package/src/components/ConfirmDialog.tsx +1 -1
  17. package/src/components/DiffSheet.stories.tsx +18 -0
  18. package/src/components/DiffSheet.tsx +38 -12
  19. package/src/components/InboxList.tsx +1 -1
  20. package/src/components/KeybindsDialog.stories.tsx +31 -0
  21. package/src/components/KeybindsDialog.tsx +42 -0
  22. package/src/components/MenuBar.stories.tsx +33 -0
  23. package/src/components/MenuBar.tsx +58 -0
  24. package/src/components/PlanSheet.stories.tsx +1 -1
  25. package/src/components/PlanSheet.tsx +21 -70
  26. package/src/components/PromptDialog.tsx +1 -1
  27. package/src/components/ReviewPanel.stories.tsx +4 -16
  28. package/src/components/ReviewPanel.tsx +10 -29
  29. package/src/components/ReviewRail.stories.tsx +39 -0
  30. package/src/components/ReviewRail.tsx +62 -57
  31. package/src/components/SettingsDialog.tsx +4 -3
  32. package/src/components/SettingsRows.stories.tsx +1 -1
  33. package/src/components/SettingsRows.tsx +1 -1
  34. package/src/components/Toast.stories.tsx +12 -0
  35. package/src/components/Toast.tsx +29 -0
  36. package/src/components/__snapshots__/stories.test.tsx.snap +598 -566
  37. package/src/components/primitives/Button.stories.tsx +2 -2
  38. package/src/components/primitives/Dialog.tsx +4 -1
  39. package/src/components/primitives/Tabs.tsx +19 -38
  40. package/src/key-bindings.test.ts +4 -4
  41. package/src/key-bindings.ts +41 -2
  42. package/src/serve.test.ts +2 -2
  43. package/src/session-controller.share.test.ts +13 -0
  44. package/src/session-controller.ts +19 -1
  45. package/src/test-support.ts +87 -23
  46. package/src/version.ts +5 -0
  47. package/src/components/ComposeBar.stories.tsx +0 -18
  48. 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, DARK.elevated],
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
- /** Resolved author display name; own notes carry none. Set = name-in-border. */
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: tokens.elevated,
126
- focusedBackgroundColor: tokens.elevated,
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
- // A collaborator's note (author set) renders as a named, bordered card; own notes stay borderless.
173
- const authored = card.authorLabel !== undefined && card.authorLabel !== "";
174
- const indent = authored ? "" : " ";
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 = authored ? 4 : 0;
184
+ const borderInset = borderLabel ? 4 : 0;
180
185
  return (
181
186
  <box
182
187
  id={id}
183
- title={authored ? ` ${card.authorLabel} ` : undefined}
188
+ title={borderLabel ? ` ${borderLabel} ` : undefined}
184
189
  style={{
185
190
  flexDirection: "column",
186
- marginBottom: 1,
187
- backgroundColor: card.isSelected ? tokens.elevated : undefined,
188
- ...(authored
189
- ? { height: cardHeight(contentRows), border: true, borderStyle: FRAME_BORDER_STYLE, borderColor: tokens.blue, paddingLeft: 1, paddingRight: 1 }
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}
@@ -94,6 +94,7 @@ export function ConfirmCard({
94
94
  title=" submit review "
95
95
  contentRows={CONFIRM_CONTENT_ROWS + (viewedSummary !== undefined ? 1 : 0)}
96
96
  borderColor={tokens.accent}
97
+ backgroundColor="transparent"
97
98
  marginRight={1}
98
99
  theme={theme}
99
100
  >
@@ -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 its card can sit directly below. */
36
- function segmentRows(rows: DiffRow[], annotatedByRow: Map<number, Annotation>): DiffSegment[] {
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(() => segmentRows(rows, annotatedByRow), [rows, annotatedByRow]);
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 style={{ flexGrow: 1, flexDirection: "column", paddingLeft: 1, paddingTop: 1 }}>
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
- <DiffChunk
232
- key={segmentIndex}
233
- segment={segment}
234
- cursor={cursor}
235
- focusedAnnotationId={focusedAnnotationId}
236
- theme={theme}
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 · ⏎ open · d delete · q quit</StatusBar>
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: () => {}, onEditRequest: () => {}, onShareRequest: () => {}, canEdit: true };
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 { Button } from "./primitives/Button";
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
- <SheetHeader
224
- session={session}
225
- onEditRequest={onEditRequest}
226
- onShareRequest={onShareRequest}
227
- canEdit={canEdit}
228
- theme={theme}
229
- />
230
- {editOrphanCount > 0 ? (
231
- <box style={{ height: 1, backgroundColor: tokens.markCommentBackground, paddingLeft: 2 }}>
232
- <text fg={tokens.red}>
233
- {editOrphanCount} annotation{editOrphanCount === 1 ? "" : "s"} no longer match - the passage was removed.
234
- </text>
235
- </box>
236
- ) : null}
237
- <scrollbox ref={scrollRef} style={{ flexGrow: 1, paddingLeft: 2, paddingTop: 1 }} focused={false}>
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
- </text>
272
- <box style={{ flexGrow: 1 }} />
273
- {canEdit && session.status !== "resolved" ? (
274
- <Toolbar>
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}>⏎ save · esc cancel</text>
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} height={24} dragging={false} rail={rail} {...panelCallbacks} />
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} height={24} dragging={false} rail={rail} {...panelCallbacks} />
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} height={24} dragging={false} rail={rail} {...panelCallbacks} />
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} height={24} dragging={false} rail={rail} {...panelCallbacks} />
71
+ <ReviewPanel mode="hidden" width={34} rail={rail} {...panelCallbacks} />
84
72
  </PanelFrame>
85
73
  ),
86
74
  size: { width: 60, height: 24 },