@cueloop/client 0.1.0-alpha.44 → 0.1.0-alpha.46

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.
@@ -16,7 +16,9 @@ import {
16
16
  blockRuns,
17
17
  displayText,
18
18
  overlayMarks,
19
- renderedOffsetFor,
19
+ renderedOffsetAtOrAfter,
20
+ renderedOffsetAtOrBefore,
21
+ safeLinkHref,
20
22
  workRangeForRendered,
21
23
  type DisplayBlock,
22
24
  type Mark,
@@ -33,6 +35,20 @@ import { FRAME_BORDER_STYLE } from "./primitives/frame";
33
35
  /** A cut block reads as removed: struck through and grayed, never red. */
34
36
  const CUT_ATTRIBUTES = createTextAttributes({ strikethrough: true, dim: true });
35
37
 
38
+ /** Inline-emphasis attributes, reused per run so the render loop allocates none. */
39
+ const STRONG_ATTRIBUTES = createTextAttributes({ bold: true });
40
+ const EM_ATTRIBUTES = createTextAttributes({ italic: true });
41
+ const STRIKE_ATTRIBUTES = createTextAttributes({ strikethrough: true });
42
+ const LINK_ATTRIBUTES = createTextAttributes({ underline: true });
43
+ /** Block-level base attributes: headings read bold, blockquotes read italic. */
44
+ const HEADING_ATTRIBUTES = createTextAttributes({ bold: true });
45
+ const QUOTE_ATTRIBUTES = createTextAttributes({ italic: true });
46
+
47
+ /** The popover toolbar card is 3 rows tall and sits flush above the marked words. */
48
+ const POPOVER_ROWS_ABOVE = 3;
49
+ /** Widest toolbar row ("comment · cut · actions · [x]") plus border and padding. */
50
+ const POPOVER_TOOLBAR_COLUMNS = 33;
51
+
36
52
  export interface PlanSelection {
37
53
  displayIndex: number;
38
54
  start: number;
@@ -73,6 +89,13 @@ export interface PlanSheetProps {
73
89
  popover: PlanPopoverState | null;
74
90
  editOrphanCount: number;
75
91
  onLineActivate: (displayIndex: number) => void;
92
+ /**
93
+ * Fires on any mouse release inside the sheet. A drag released outside a
94
+ * block's text (the gutter, past a line end, a gap between blocks) never
95
+ * reaches a block's own handler, so this fallback lets the app still turn
96
+ * the finished drag's native selection into a span.
97
+ */
98
+ onSelectionRelease?: () => void;
76
99
  theme?: Theme;
77
100
  }
78
101
 
@@ -108,6 +131,7 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
108
131
  popover,
109
132
  editOrphanCount,
110
133
  onLineActivate,
134
+ onSelectionRelease,
111
135
  theme,
112
136
  }: PlanSheetProps,
113
137
  handleRef,
@@ -146,9 +170,11 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
146
170
  if (!renderer) return;
147
171
  const blockRef = blockRefs.current.get(span.displayIndex);
148
172
  if (!blockRef) return;
149
- const renderedStart = renderedOffsetFor(blockRef.runs, span.start);
150
- const renderedEnd = renderedOffsetFor(blockRef.runs, span.end - 1);
151
- if (renderedStart === null || renderedEnd === null) return;
173
+ // a span over an emphasized word starts/ends on concealed markers with
174
+ // no rendered cell; snap to the visible characters inside them
175
+ const renderedStart = renderedOffsetAtOrAfter(blockRef.runs, span.start);
176
+ const renderedEnd = renderedOffsetAtOrBefore(blockRef.runs, span.end - 1);
177
+ if (renderedStart === null || renderedEnd === null || renderedEnd < renderedStart) return;
152
178
  const startPosition = positionOfRenderedOffset(blockRef.renderable, renderedStart);
153
179
  const endPosition = positionOfRenderedOffset(blockRef.renderable, renderedEnd);
154
180
  renderer.startSelection(blockRef.renderable, startPosition.x, startPosition.y);
@@ -166,24 +192,43 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
166
192
  },
167
193
  }));
168
194
 
169
- // the popover floats above its block, anchored to the selection start; flip
170
- // below when the block sits too near the viewport top to fit above it
171
- const [popoverFlipBelow, setPopoverFlipBelow] = useState(false);
195
+ // the popover sits flush above the marked words, anchored to the
196
+ // selection start mapped through the wrap geometry (line + column, not the
197
+ // block's linear offset). It renders as the LAST child of the scrollbox in
198
+ // content coordinates: content coordinates are scroll-invariant so the card
199
+ // tracks its block, and the last sibling paints over every block it floats
200
+ // across. Flips below when the line sits too near the viewport top.
201
+ const [popoverTop, setPopoverTop] = useState(0);
172
202
  const [popoverLeft, setPopoverLeft] = useState(2);
203
+ // a terminal resize rewraps every block; re-run the anchor math against the
204
+ // new geometry instead of leaving the card at its pre-resize cell
205
+ const [resizeEpoch, setResizeEpoch] = useState(0);
206
+ useEffect(() => {
207
+ if (!renderer) return;
208
+ const onResize = (): void => setResizeEpoch((epoch) => epoch + 1);
209
+ renderer.on("resize", onResize);
210
+ return () => void renderer.off("resize", onResize);
211
+ }, [renderer]);
173
212
  useEffect(() => {
174
213
  if (!popover) return;
175
214
  const blockRef = blockRefs.current.get(popover.displayIndex);
176
215
  const scrollbox = scrollRef.current;
177
216
  if (!blockRef || !scrollbox) return;
178
- // only the toolbar (3 rows) + gap must fit above; the dropdown flows down
179
- setPopoverFlipBelow(blockRef.renderable.y - scrollbox.y < 4);
180
- // left-anchor to the start of the selection: gutter width + its column
181
- const startColumn =
217
+ const renderedStart =
182
218
  activeSpan && activeSpan.displayIndex === popover.displayIndex
183
- ? (renderedOffsetFor(blockRef.runs, activeSpan.start) ?? 0)
219
+ ? (renderedOffsetAtOrAfter(blockRef.runs, activeSpan.start) ?? 0)
184
220
  : 0;
185
- setPopoverLeft(2 + startColumn);
186
- }, [popover, activeSpan, display, cursor]);
221
+ const startPosition = positionOfRenderedOffset(blockRef.renderable, renderedStart);
222
+ const content = scrollbox.content;
223
+ const anchorLeft = startPosition.x - content.x;
224
+ const anchorTop = startPosition.y - content.y;
225
+ // clamp so the toolbar card never runs past the content's right edge
226
+ const maxLeft = Math.max(0, content.width - POPOVER_TOOLBAR_COLUMNS);
227
+ setPopoverLeft(Math.min(anchorLeft, maxLeft));
228
+ // only the toolbar (3 rows) + gap must fit above; the dropdown flows down
229
+ const lineViewportRow = startPosition.y - scrollbox.y;
230
+ setPopoverTop(anchorTop + (lineViewportRow < POPOVER_ROWS_ABOVE ? 1 : -POPOVER_ROWS_ABOVE));
231
+ }, [popover, activeSpan, display, cursor, resizeEpoch]);
187
232
 
188
233
  const registerBlock = (
189
234
  displayIndex: number,
@@ -235,13 +280,7 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
235
280
  <box
236
281
  key={displayIndex}
237
282
  id={`plan-block-${displayIndex}`}
238
- // raise the block holding the popover so its overlay paints over the
239
- // later blocks it floats across (siblings paint in z-index order)
240
- style={{
241
- flexDirection: "row",
242
- marginTop: gap,
243
- zIndex: popover?.displayIndex === displayIndex ? 10 : undefined,
244
- }}
283
+ style={{ flexDirection: "row", marginTop: gap }}
245
284
  >
246
285
  <text selectable={false}>
247
286
  <span fg={isCursor ? tokens.accent : tokens.textDim}>{isCursor ? "▎ " : " "}</span>
@@ -258,31 +297,22 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
258
297
  }
259
298
  onMouseUp={() => onLineActivate(displayIndex)}
260
299
  >
261
- {runs.map((run, runIndex) => (
262
- <span key={runIndex} {...runStyle(run, block, tokens)}>
263
- {run.text}
264
- </span>
265
- ))}
300
+ {runs.map((run, runIndex) => {
301
+ const url = run.role === "link" ? safeLinkHref(run.href) : undefined;
302
+ return (
303
+ <span
304
+ key={runIndex}
305
+ {...runStyle(run, block, tokens)}
306
+ {...(url ? { link: { url } } : {})}
307
+ >
308
+ {run.text}
309
+ </span>
310
+ );
311
+ })}
266
312
  {showsChangeTag(block) ? (
267
313
  <span fg={tagColor(block, tokens)}> [{tagLabel(block)}]</span>
268
314
  ) : null}
269
315
  </text>
270
- {popover && popover.displayIndex === displayIndex ? (
271
- // float the card over the block, centered, with a one-row gap; flip
272
- // below only when there is no room above (near the viewport top)
273
- <box
274
- style={{
275
- position: "absolute",
276
- left: popoverLeft,
277
- // fixed offset: the toolbar sits one row above the selection and
278
- // stays put; the dropdown flows down from it, over the selection
279
- top: popoverFlipBelow ? 1 : -4,
280
- flexDirection: "column",
281
- }}
282
- >
283
- <MarkerPopover {...popover} theme={theme} />
284
- </box>
285
- ) : null}
286
316
  </box>,
287
317
  );
288
318
  }
@@ -298,9 +328,26 @@ export const PlanSheet = forwardRef<PlanSheetHandle, PlanSheetProps>(function Pl
298
328
  );
299
329
  }
300
330
  }
331
+ if (popover) {
332
+ // last child on purpose: later siblings paint on top, so the floating card
333
+ // covers every block it overlaps regardless of scroll position
334
+ children.push(
335
+ <box
336
+ key="marker-popover"
337
+ style={{
338
+ position: "absolute",
339
+ left: popoverLeft,
340
+ top: popoverTop,
341
+ flexDirection: "column",
342
+ }}
343
+ >
344
+ <MarkerPopover {...popover} theme={theme} />
345
+ </box>,
346
+ );
347
+ }
301
348
 
302
349
  return (
303
- <box style={{ flexGrow: 1, flexDirection: "column" }}>
350
+ <box style={{ flexGrow: 1, flexDirection: "column" }} onMouseUp={onSelectionRelease}>
304
351
  <box
305
352
  style={{
306
353
  flexGrow: 1,
@@ -366,15 +413,25 @@ function runStyle(
366
413
  ): { fg?: string; bg?: string; attributes?: number } {
367
414
  // a cut block reads as removed: every run struck through and grayed, never red
368
415
  if (block.type === "del") return { fg: tokens.textDim, attributes: CUT_ATTRIBUTES };
416
+ const isHeading = block.kind === "h1" || block.kind === "h2" || block.kind === "h3";
417
+ const isQuote = block.kind === "quote";
418
+ // headings are all bold; level reads from descending brightness alone (a
419
+ // terminal cannot scale font size), leaving the salmon accent to annotations
369
420
  const headingFg =
370
421
  block.kind === "h1"
371
422
  ? tokens.text
372
- : block.kind === "h2" || block.kind === "h3"
373
- ? tokens.accent
374
- : undefined;
423
+ : block.kind === "h2"
424
+ ? tokens.textMuted
425
+ : block.kind === "h3"
426
+ ? tokens.textDim
427
+ : undefined;
428
+ // block-level base: headings bold, quotes muted italic; inline roles compose on top
429
+ const baseFg = headingFg ?? (isQuote ? tokens.textMuted : tokens.text);
430
+ const baseAttributes = (isHeading ? HEADING_ATTRIBUTES : 0) | (isQuote ? QUOTE_ATTRIBUTES : 0);
431
+ const withBase = (roleAttributes: number): number => baseAttributes | roleAttributes;
375
432
  switch (run.role) {
376
433
  case "ins":
377
- return { fg: tokens.insertedForeground };
434
+ return { fg: tokens.insertedForeground, attributes: baseAttributes || undefined };
378
435
  case "del":
379
436
  return { fg: tokens.deletedForeground };
380
437
  case "mark-comment":
@@ -383,7 +440,17 @@ function runStyle(
383
440
  return { fg: tokens.accentInk, bg: tokens.accent };
384
441
  case "kspan":
385
442
  return { fg: tokens.accentInk, bg: tokens.accent };
443
+ case "strong":
444
+ return { fg: baseFg, attributes: withBase(STRONG_ATTRIBUTES) };
445
+ case "em":
446
+ return { fg: baseFg, attributes: withBase(EM_ATTRIBUTES) };
447
+ case "code":
448
+ return { fg: tokens.text, bg: tokens.elevated };
449
+ case "strike":
450
+ return { fg: tokens.textMuted, attributes: withBase(STRIKE_ATTRIBUTES) };
451
+ case "link":
452
+ return { fg: tokens.blue, attributes: withBase(LINK_ATTRIBUTES) };
386
453
  default:
387
- return { fg: headingFg ?? tokens.text };
454
+ return { fg: baseFg, attributes: baseAttributes || undefined };
388
455
  }
389
456
  }
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { PrototypeSheet } from "./PrototypeSheet";
5
+
6
+ export const meta: StoryMeta = { title: "PrototypeSheet" };
7
+
8
+ // The story renderer reports no kitty graphics, so the sheet shows its fallback.
9
+ export const Unsupported: Story = {
10
+ render: () => (
11
+ <PrototypeSheet
12
+ prototypePath="/tmp/example.html"
13
+ quickActions={[]}
14
+ canComment={true}
15
+ onCommentElement={() => {}}
16
+ />
17
+ ),
18
+ expectedColors: [DARK.textDim],
19
+ size: { width: 80, height: 24 },
20
+ };
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Prototype review surface: renders the HTML as an image and turns a click into
3
+ * a DOM-element selection, so the shared marker actions bar and compose card
4
+ * annotate an element rather than a text span.
5
+ */
6
+
7
+ import React, { useEffect, useRef, useState } from "react";
8
+ import type { ImageRenderable } from "@opentui/core";
9
+ import { useRenderer } from "@opentui/react";
10
+ import type { Theme } from "../theme";
11
+ import { quickActionBody, type QuickAction } from "../config";
12
+ import { useComponentTheme } from "./theme-context";
13
+ import { FRAME_BORDER_STYLE } from "./primitives/frame";
14
+ import { MarkerPopover } from "./MarkerPopover";
15
+ import { AnnotationCard } from "./AnnotationCard";
16
+ import {
17
+ cssBoxToCell,
18
+ imageCellToCss,
19
+ launchPrototypeRenderer,
20
+ type PrototypeElement,
21
+ type PrototypeRenderer,
22
+ type PrototypeViewport,
23
+ } from "../prototype-browser";
24
+
25
+ export interface PrototypeSheetProps {
26
+ prototypePath: string;
27
+ quickActions: QuickAction[];
28
+ canComment: boolean;
29
+ onCommentElement: (element: PrototypeElement, body: string) => void;
30
+ theme?: Theme;
31
+ }
32
+
33
+ type SheetStatus = "loading" | "ready" | "unsupported" | "error";
34
+
35
+ const DEFAULT_CELL_ASPECT = 2;
36
+ const POPOVER_ROWS = 3;
37
+
38
+ export function PrototypeSheet({
39
+ prototypePath,
40
+ quickActions,
41
+ canComment,
42
+ onCommentElement,
43
+ theme,
44
+ }: PrototypeSheetProps): React.ReactNode {
45
+ const tokens = useComponentTheme(theme);
46
+ const renderer = useRenderer();
47
+ const imageRef = useRef<ImageRenderable | null>(null);
48
+ const browserRef = useRef<PrototypeRenderer | null>(null);
49
+ const launchedRef = useRef(false);
50
+ const viewportRef = useRef<PrototypeViewport>({ width: 1280, height: 800 });
51
+
52
+ const [status, setStatus] = useState<SheetStatus>("loading");
53
+ const [errorMessage, setErrorMessage] = useState<string>("");
54
+ const [png, setPng] = useState<Uint8Array | null>(null);
55
+ const [selected, setSelected] = useState<PrototypeElement | null>(null);
56
+ const [overlayCell, setOverlayCell] = useState<{ left: number; top: number } | null>(null);
57
+ const [actionsOpen, setActionsOpen] = useState(false);
58
+ const [composing, setComposing] = useState(false);
59
+ const [draftText, setDraftText] = useState("");
60
+
61
+ useEffect(() => {
62
+ return () => {
63
+ void browserRef.current?.close();
64
+ };
65
+ }, []);
66
+
67
+ const viewportFor = (columns: number, rows: number): PrototypeViewport => {
68
+ const resolution = renderer?.resolution ?? null;
69
+ const cellWidth = resolution ? resolution.width / renderer!.terminalWidth : 1;
70
+ const cellHeight = resolution
71
+ ? resolution.height / renderer!.terminalHeight
72
+ : DEFAULT_CELL_ASPECT;
73
+ return {
74
+ width: Math.max(320, Math.round(columns * cellWidth)),
75
+ height: Math.max(240, Math.round(rows * cellHeight)),
76
+ };
77
+ };
78
+
79
+ const launch = (): void => {
80
+ const image = imageRef.current;
81
+ if (launchedRef.current || !image || image.width < 1 || image.height < 1) return;
82
+ if (renderer?.capabilities && renderer.capabilities.kitty_graphics === false) {
83
+ setStatus("unsupported");
84
+ return;
85
+ }
86
+ launchedRef.current = true;
87
+ const viewport = viewportFor(image.width, image.height);
88
+ viewportRef.current = viewport;
89
+ void (async () => {
90
+ try {
91
+ const browser = await launchPrototypeRenderer({ filePath: prototypePath, viewport });
92
+ browserRef.current = browser;
93
+ setPng(await browser.screenshot());
94
+ setStatus("ready");
95
+ } catch (error) {
96
+ setErrorMessage(error instanceof Error ? error.message : String(error));
97
+ setStatus("error");
98
+ }
99
+ })();
100
+ };
101
+
102
+ const refresh = async (): Promise<void> => {
103
+ const browser = browserRef.current;
104
+ if (browser) setPng(await browser.screenshot());
105
+ };
106
+
107
+ const clearSelection = (): void => {
108
+ setSelected(null);
109
+ setOverlayCell(null);
110
+ setActionsOpen(false);
111
+ setComposing(false);
112
+ setDraftText("");
113
+ void browserRef.current
114
+ ?.highlight(null)
115
+ .then(refresh)
116
+ .catch(() => undefined);
117
+ };
118
+
119
+ const onImageMouseDown = (event: { x: number; y: number }): void => {
120
+ const browser = browserRef.current;
121
+ const image = imageRef.current;
122
+ if (status !== "ready" || !browser || !image) return;
123
+ const geometry = { x: image.x, y: image.y, width: image.width, height: image.height };
124
+ const cssPoint = imageCellToCss(event, geometry, viewportRef.current);
125
+ if (!cssPoint) return;
126
+ void (async () => {
127
+ const element = await browser.elementAt(cssPoint.x, cssPoint.y);
128
+ if (!element) return;
129
+ const cell = cssBoxToCell(element.box, geometry, viewportRef.current);
130
+ const topRaw = cell.row - geometry.y;
131
+ setSelected(element);
132
+ setOverlayCell({
133
+ left: Math.max(0, cell.column - geometry.x),
134
+ top: Math.max(0, topRaw < POPOVER_ROWS ? topRaw + 1 : topRaw - POPOVER_ROWS),
135
+ });
136
+ setActionsOpen(false);
137
+ setComposing(false);
138
+ await browser.highlight(element.selector);
139
+ await refresh();
140
+ })().catch((error) => {
141
+ setErrorMessage(error instanceof Error ? error.message : String(error));
142
+ setStatus("error");
143
+ });
144
+ };
145
+
146
+ const commit = (body: string): void => {
147
+ if (!canComment) return;
148
+ if (selected && body.trim()) onCommentElement(selected, body.trim());
149
+ clearSelection();
150
+ };
151
+
152
+ const overlayLeft = overlayCell?.left ?? 0;
153
+ const overlayTop = Math.max(0, overlayCell?.top ?? 0);
154
+ const overlayStyle = {
155
+ position: "absolute" as const,
156
+ left: overlayLeft,
157
+ top: overlayTop,
158
+ flexDirection: "column" as const,
159
+ };
160
+
161
+ return (
162
+ <box
163
+ style={{
164
+ flexGrow: 1,
165
+ flexDirection: "column",
166
+ border: true,
167
+ borderStyle: FRAME_BORDER_STYLE,
168
+ borderColor: tokens.text,
169
+ }}
170
+ >
171
+ <image
172
+ ref={(instance: ImageRenderable | null) => {
173
+ imageRef.current = instance;
174
+ }}
175
+ {...(png ? { source: png } : {})}
176
+ fit="fill"
177
+ protocol="auto"
178
+ style={{ flexGrow: 1 }}
179
+ onSizeChange={launch}
180
+ onMouseDown={onImageMouseDown}
181
+ />
182
+ {status !== "ready" ? (
183
+ <box style={{ position: "absolute", left: 2, top: 1 }}>
184
+ <text fg={tokens.textDim}>{statusLine(status, errorMessage)}</text>
185
+ </box>
186
+ ) : null}
187
+ {selected && !composing ? (
188
+ <box style={overlayStyle}>
189
+ <MarkerPopover
190
+ view={actionsOpen ? "actions" : "toolbar"}
191
+ actions={quickActions}
192
+ actionIndex={0}
193
+ canCut={false}
194
+ onComment={() => (canComment ? setComposing(true) : undefined)}
195
+ onCut={() => undefined}
196
+ onOpenActions={() => setActionsOpen(true)}
197
+ onClose={clearSelection}
198
+ onPickAction={(index) => commit(quickActionBody(quickActions[index]!))}
199
+ onBack={() => setActionsOpen(false)}
200
+ theme={theme}
201
+ />
202
+ </box>
203
+ ) : null}
204
+ {selected && composing ? (
205
+ <box style={overlayStyle}>
206
+ <AnnotationCard
207
+ kind="comment"
208
+ quote={selected.quote}
209
+ draft={{
210
+ text: draftText,
211
+ onInput: setDraftText,
212
+ onSave: () => commit(draftText),
213
+ onCancel: clearSelection,
214
+ }}
215
+ theme={theme}
216
+ />
217
+ </box>
218
+ ) : null}
219
+ </box>
220
+ );
221
+ }
222
+
223
+ function statusLine(status: SheetStatus, errorMessage: string): string {
224
+ if (status === "loading") return "rendering prototype…";
225
+ if (status === "unsupported")
226
+ return "prototype preview needs a graphics terminal (kitty or ghostty)";
227
+ return `prototype preview failed: ${errorMessage}`;
228
+ }
@@ -177,8 +177,6 @@ export const SubmitConfirmOpen: Story = {
177
177
  submitConfirm={{
178
178
  verdict: "request_changes",
179
179
  summary: "",
180
- annotationCount: 2,
181
- blockingCount: 0,
182
180
  onInput: () => {},
183
181
  onSelectVerdict: () => {},
184
182
  onSubmit: () => {},
@@ -75,11 +75,6 @@ export interface ReviewRailProps {
75
75
  theme?: Theme;
76
76
  }
77
77
 
78
- /** Forward-compatible: open annotation kinds may carry a blocking flag. */
79
- export function annotationBlocking(annotation: Annotation): boolean {
80
- return (annotation as Annotation & { blocking?: boolean }).blocking === true;
81
- }
82
-
83
78
  export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function ReviewRail(
84
79
  {
85
80
  session,
@@ -133,15 +128,13 @@ export const ReviewRail = forwardRef<ReviewRailHandle, ReviewRailProps>(function
133
128
  body: annotation.body,
134
129
  isSelected: annotation.id === selectedId,
135
130
  isOrphan: resolvedIds !== null && !resolvedIds.has(annotation.id),
136
- isBlocking: annotationBlocking(annotation),
137
131
  // border-title author: a collaborator's name, "agent" for agent notes, else "me"
138
132
  author: annotation.author
139
133
  ? resolveDisplayName(annotation.author, session.participants, authorNames)
140
134
  : isAgentNote(annotation)
141
135
  ? "agent"
142
136
  : "me",
143
- // own + agent notes read as salmon; a share collaborator's note as blue
144
- tone: annotation.author && !isAgentNote(annotation) ? tokens.blue : tokens.accent,
137
+ tone: tokens.blue,
145
138
  editing:
146
139
  cardEdit && cardEdit.id === annotation.id
147
140
  ? {
@@ -296,7 +289,7 @@ function RemovalCard({
296
289
  title={title}
297
290
  contentRows={contentRows}
298
291
  borderColor={tokens.red}
299
- backgroundColor={isSelected ? tokens.elevated : "transparent"}
292
+ backgroundColor="transparent"
300
293
  theme={theme}
301
294
  >
302
295
  {previewLines.length === 0 ? (
@@ -84,8 +84,8 @@ exports[`stories catalog AnnotationCard/SavedEditing renders, snapshots, and car
84
84
  "
85
85
  `;
86
86
 
87
- exports[`stories catalog AnnotationCard/SavedOrphanBlocking renders, snapshots, and carries its colors: AnnotationCard/SavedOrphanBlocking 1`] = `
88
- "╭─ COMMENT · me · BLOCKING · ORPHANED ─────────────────────────────────────────╮
87
+ exports[`stories catalog AnnotationCard/SavedOrphan renders, snapshots, and carries its colors: AnnotationCard/SavedOrphan 1`] = `
88
+ "╭─ COMMENT · me · ORPHANED ────────────────────────────────────────────────────╮
89
89
  │ "a removed passage" │
90
90
  │ The passage is gone. │
91
91
  ╰──────────────────────────────────────────────────────────────────────────────╯
@@ -338,8 +338,6 @@ exports[`stories catalog CompletionOverlay/FeedbackCounting renders, snapshots,
338
338
 
339
339
  exports[`stories catalog ConfirmCard/ApproveDefault renders, snapshots, and carries its colors: ConfirmCard/ApproveDefault 1`] = `
340
340
  "╭─ submit review ─────────────────────╮
341
- │ 0 annotations · 0 blocking │
342
- │ │
343
341
  │ Comment [Approve] Changes │
344
342
  │ │
345
343
  │ summary for the agent (optional) │
@@ -351,13 +349,13 @@ exports[`stories catalog ConfirmCard/ApproveDefault renders, snapshots, and carr
351
349
 
352
350
 
353
351
 
352
+
353
+
354
354
  "
355
355
  `;
356
356
 
357
357
  exports[`stories catalog ConfirmCard/RequestChanges renders, snapshots, and carries its colors: ConfirmCard/RequestChanges 1`] = `
358
358
  "╭─ submit review ─────────────────────╮
359
- │ 3 annotations · 1 blocking │
360
- │ │
361
359
  │ Comment Approve [Changes] │
362
360
  │ │
363
361
  │ Tighten the steps. │
@@ -369,12 +367,13 @@ exports[`stories catalog ConfirmCard/RequestChanges renders, snapshots, and carr
369
367
 
370
368
 
371
369
 
370
+
371
+
372
372
  "
373
373
  `;
374
374
 
375
375
  exports[`stories catalog ConfirmCard/WithViewedSummary renders, snapshots, and carries its colors: ConfirmCard/WithViewedSummary 1`] = `
376
376
  "╭─ submit review ─────────────────────╮
377
- │ 1 annotations · 0 blocking │
378
377
  │ 2/3 files viewed │
379
378
  │ │
380
379
  │ Comment [Approve] Changes │
@@ -387,6 +386,7 @@ exports[`stories catalog ConfirmCard/WithViewedSummary renders, snapshots, and c
387
386
 
388
387
 
389
388
 
389
+
390
390
  "
391
391
  `;
392
392
 
@@ -1121,9 +1121,9 @@ exports[`stories catalog ReviewRail/SubmitConfirmOpen renders, snapshots, and ca
1121
1121
 
1122
1122
 
1123
1123
 
1124
+
1125
+
1124
1126
  ╭─ submit review ───────────────╮
1125
- │ 2 annotations · 0 blocking │
1126
- │ │
1127
1127
  │ Comment Approve [Changes] │
1128
1128
  │ │
1129
1129
  │ summary for the agent (optiona│
@@ -1825,5 +1825,33 @@ exports[`stories catalog theme-context/ProviderSwap renders, snapshots, and carr
1825
1825
 
1826
1826
 
1827
1827
 
1828
+ "
1829
+ `;
1830
+
1831
+ exports[`stories catalog PrototypeSheet/Unsupported renders, snapshots, and carries its colors: PrototypeSheet/Unsupported 1`] = `
1832
+ "╭──────────────────────────────────────────────────────────────────────────────╮
1833
+ │ │
1834
+ │ rendering prototype… │
1835
+ │ │
1836
+ │ │
1837
+ │ │
1838
+ │ │
1839
+ │ │
1840
+ │ │
1841
+ │ │
1842
+ │ │
1843
+ │ │
1844
+ │ │
1845
+ │ │
1846
+ │ │
1847
+ │ │
1848
+ │ │
1849
+ │ │
1850
+ │ │
1851
+ │ │
1852
+ │ │
1853
+ │ │
1854
+ │ │
1855
+ ╰──────────────────────────────────────────────────────────────────────────────╯
1828
1856
  "
1829
1857
  `;
@@ -32,6 +32,7 @@ function baseController() {
32
32
  curationItems: mock(() => []),
33
33
  restoreCuration: mock(),
34
34
  annotate: mock(),
35
+ annotatePrototype: mock(),
35
36
  updateAnnotation: mock(),
36
37
  removeAnnotation: mock(),
37
38
  walkStart: mock(),