@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.
Files changed (61) hide show
  1. package/package.json +5 -5
  2. package/src/App.autoclose.test.tsx +4 -3
  3. package/src/App.diff.test.tsx +5 -2
  4. package/src/App.export.test.tsx +5 -7
  5. package/src/App.plan-review.test.tsx +14 -17
  6. package/src/App.readonly.test.tsx +9 -7
  7. package/src/App.rendering.test.tsx +6 -2
  8. package/src/App.review-panel.test.tsx +19 -14
  9. package/src/App.submit-confirm.test.tsx +6 -7
  10. package/src/App.test.tsx +8 -5
  11. package/src/App.toast.test.tsx +68 -0
  12. package/src/App.tsx +154 -50
  13. package/src/App.walk.test.tsx +7 -4
  14. package/src/components/AnnotationCard.stories.tsx +1 -1
  15. package/src/components/AnnotationCard.tsx +18 -13
  16. package/src/components/CompletionOverlay.tsx +1 -1
  17. package/src/components/ConfirmCard.tsx +1 -0
  18. package/src/components/ConfirmDialog.stories.tsx +1 -1
  19. package/src/components/ConfirmDialog.tsx +1 -1
  20. package/src/components/DiffSheet.stories.tsx +21 -3
  21. package/src/components/DiffSheet.tsx +45 -19
  22. package/src/components/InboxList.stories.tsx +1 -1
  23. package/src/components/InboxList.tsx +3 -3
  24. package/src/components/KeybindsDialog.stories.tsx +31 -0
  25. package/src/components/KeybindsDialog.tsx +42 -0
  26. package/src/components/MenuBar.stories.tsx +33 -0
  27. package/src/components/MenuBar.tsx +58 -0
  28. package/src/components/PlanSheet.stories.tsx +2 -2
  29. package/src/components/PlanSheet.tsx +26 -75
  30. package/src/components/PromptDialog.stories.tsx +2 -2
  31. package/src/components/PromptDialog.tsx +1 -1
  32. package/src/components/ReviewPanel.stories.tsx +5 -17
  33. package/src/components/ReviewPanel.tsx +10 -29
  34. package/src/components/ReviewRail.stories.tsx +39 -0
  35. package/src/components/ReviewRail.tsx +62 -57
  36. package/src/components/SettingsDialog.stories.tsx +1 -1
  37. package/src/components/SettingsDialog.tsx +4 -3
  38. package/src/components/SettingsRows.stories.tsx +1 -1
  39. package/src/components/SettingsRows.tsx +1 -1
  40. package/src/components/Toast.stories.tsx +12 -0
  41. package/src/components/Toast.tsx +29 -0
  42. package/src/components/WalkWizard.stories.tsx +1 -1
  43. package/src/components/WalkWizard.tsx +1 -1
  44. package/src/components/__snapshots__/stories.test.tsx.snap +598 -566
  45. package/src/components/primitives/Button.stories.tsx +2 -2
  46. package/src/components/primitives/Dialog.stories.tsx +1 -1
  47. package/src/components/primitives/Dialog.tsx +7 -5
  48. package/src/components/primitives/Tabs.tsx +19 -38
  49. package/src/components/stories-app.tsx +1 -1
  50. package/src/components/stories.test.tsx +2 -0
  51. package/src/key-bindings.test.ts +4 -4
  52. package/src/key-bindings.ts +41 -2
  53. package/src/serve.test.ts +2 -2
  54. package/src/session-controller.share.test.ts +13 -0
  55. package/src/session-controller.ts +19 -1
  56. package/src/test-support.test.ts +55 -0
  57. package/src/test-support.ts +113 -23
  58. package/src/theme.ts +16 -16
  59. package/src/version.ts +5 -0
  60. package/src/components/ComposeBar.stories.tsx +0 -18
  61. 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 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);
@@ -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.markCommentBg });
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.cursorBg, content: tokens.cursorBg });
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.bg}
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.insFg : row.kind === "del" ? tokens.delFg : tokens.textMuted;
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.cursorBg : isAnnotatedRow ? tokens.markCommentBg : undefined}
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(() => 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,33 +224,43 @@ 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") {
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.cursorBg : tokens.panel}>
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.cursorBg : undefined}>
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
- <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>
@@ -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.cursorBg],
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.bg, padding: 1 }}>
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.cursorBg : undefined }}>
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 · ⏎ 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: () => (
@@ -21,7 +21,7 @@ export const AnnotatedPlan: Story = {
21
21
  {...callbacks}
22
22
  />
23
23
  ),
24
- expectedColors: [DARK.markCommentBg, DARK.markSuggestionBg, DARK.cursorBg],
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 { 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,
@@ -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.cursorBg : undefined}
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
- <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.markCommentBg, 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 {
@@ -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.insFg };
266
+ return { fg: tokens.insertedForeground };
316
267
  case "del":
317
- return { fg: tokens.delFg };
268
+ return { fg: tokens.deletedForeground };
318
269
  case "mark-comment":
319
- return { fg: tokens.text, bg: tokens.markCommentBg };
270
+ return { fg: tokens.text, bg: tokens.markCommentBackground };
320
271
  case "mark-suggestion":
321
- return { fg: tokens.text, bg: tokens.markSuggestionBg };
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.backdrop, DARK.elevated, DARK.accent],
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.backdrop, DARK.elevated],
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}>⏎ save · esc cancel</text>
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.bg }}>
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} 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 },