event-detail¶
raw source¶
The human's measuring hand on a recorded user loop: watch the clip, point at the moment something is
wrong, say what — and have that judgment land where it belongs, as one durable, conversable thing. This is
an authoring surface over an already-captured reading; eval still runs nothing, and no new ledger
structure exists for its sake. "Annotator" was never a real concept (U1): an annotation is just an anchored
remark on a video host, and the pane that shows it is just the evidence+reply detail. There is ONE
EventDetail component; with the master-detail era gone it has ONE home — the Evals DETAIL page
(evals-view), project- or session-scoped — and it wears GitHub's issue-page grammar there
(review-chrome): evidence is the "description+activity" main column, reading metadata the side rail.
expanded spec¶
EventDetail fills the review-chrome DetailShell for one (node, scenario): the header names the
scenario (title) with the node as its trailing meta; the status band carries the verdict badge and the
A/B strip; the MAIN column is the evidence workspace then the remark thread with its composer
docked at the foot; the SIDE rail is the reading/session metadata, every value through
review-chrome's ONE SideValue primitive (shrink + ellipsis, full text on the tooltip — a filer UUID
never stretches the rail) — evaluator, filed time, the reading's spec node as a REAL labeled ref
(the shell's graph-focus door; a host wiring none still shows the labeled value), the FILER
chip with its liveness dot (alive = the session is on the board and not offline, painted by the live
STATUS_COLOR, state; a live chip click-throughs to #/sessions/<id>; a legacy reading without by
shows none), the human-ok settled mark when signed, and the stale readout for a non-fresh viewed
reading — the freshness axes that moved, and for the code axis which governed files drifted by how many
commits (eval-core's codeDrift; reporting only, never deciding) — and, when the page supplies
neighbors, the continue-reviewing queue at the rail's foot (paged-review projects at most five
lightweight neighbors from the source's stable default order, split into Previous / Up next positional
groups): each row a REAL detail
anchor wearing the ONE shared verdict visual plus its
scenario and node, an empty group rendering no heading, no private selection state, the whole section
absent when no neighbor exists. At phone
width the side metadata reflows ABOVE the workspace in one column (review-chrome), and the composer
stays a sticky bar at the
column's foot. The composer's review identity is the source scope + (node, scenario) + currently viewed
reading: an unrelated board repaint keeps that identity, the A/B cursor, timeline, ordinary typed prose,
and anchored prefill intact; changing scope, scenario, or A/B reading changes it and remounts the composer,
so every kind of draft dies before the new evidence is reviewable. A draft can never post onto another
scope, scenario, or reading.
A reading's evidence is a LIST, so every entry renders on the ONE stage — and U1's "one evidence
detail" is literal code: the per-entry renderer is the extracted Evidence.jsx — one kind-dispatch
(EvidenceItem: video → an inline player, image → click-to-enlarge lightbox whose Esc is swallowed,
transcript → text, a pruned blob → the honest miss sentinel) reused verbatim by the node eval tab's
gallery and by a reply's inline blob links (kind resolved from the blob route's served Content-Type).
Media keeps its INTRINSIC geometry (GitHub's detail-media behavior): an image or clip renders at its
native size — inline-size: auto capped by max-inline-size: 100% with block-size: auto — so a piece
larger than the main column's available width scales down proportionally to fit, and a smaller one keeps
its native pixels, never stretched or upscaled to fill the column. That law covers every media home alike
(the clip stage, the still gallery, a reply's inline frames), no home may flex-stretch its children wide,
and no evidence — however oversized or tall, at any viewport down to phone width — ever widens the page.
A shrunk clip keeps its full custom review-track controls and timeline; the player chrome shrink-wraps
the clip it plays rather than stretching the clip to fill the column, keeping only a small usability
floor for the control bar — under a tiny clip the scrubber never crushes to a sliver (the floor widens
the bar alone; the clip still renders at its native size). The
ONE deliberate specialization is this workspace's clip player: the video plays under a custom
review-track scrubber — native chrome replaced so the timeline can carry the review: anchored remarks
are markers on it, the playhead lights the remark it is inside, and clicking a marker (or an anchored
remark in the thread) seeks. The surface is keyboard-driven — play/pause, coarse and frame-fine
scrubbing, ↑/↓ between remarks, a = annotate the current frame — and the bar carries the ONE shared
FullscreenButton (native chrome is suppressed here; every plain <video controls> home keeps its native
door). The workspace renders no always-visible instructional caption strip — no usage-hint line and
no keyboard-shortcut legend under the player (the same restraint the docked composer keeps): the
affordances are self-evident and the evidence stays uncluttered, so nothing but the reading is ever baked
into a captured frame or screenshot of the stage. A structured data entry folds behind its header when the reading also carries media
(evidence-kind-taxonomy); a blob-less reading shows its verdict note as the body, never an empty box.
With a step-timeline sidecar, a step ruler renders naming each step at its axis position
(time→m:ss, frame→#123, line→L42, index→3/N) — on a video the scrubber also bands the boundaries and a
live chip names the current step; a non-video reading with a sidecar shows its ruler too. Without a
sidecar it is a plain evidence view with remarks — degraded gracefully, never blocked.
The A/B strip — a scenario's fail→pass lifecycle, walkable in place, BOUNDED to one line. A bug fix
leaves a pair of readings (reproduce-before-fix): the A (reproduced failure) and the B (verified fix).
The status band renders the viewed verdict and the scenario's WHOLE history through review-chrome's
ONE shared icon/label/tone mapping (oldest→newest), the viewed one lit, with shared chevron buttons to
walk — flipping swaps the media, expected, note, and badge in place, so A and B sit one keystroke apart.
The strip is a single line at a stable height however many readings accrue: at most EIGHT recent
readings render as pips, and when the viewed reading falls outside that recent window it takes one of
the eight slots (leftmost — it is the oldest shown), clearly selected, so the current reading is always
visible. Every reading not holding a pip collapses behind ONE accessible overflow menu (the shared
popover mechanics — menuitemradio rows wearing the same shared verdict visual plus position and filed
time; picking a row views that reading), so a hundreds-deep history neither wraps the band tall nor loses
any reading's reach, and no walk or pick ever changes the band's height. The history is
SCOPE-PROVIDED, sharing the page's root: paged-review's bounded detail response supplies only the
addressed scenario's complete history from either trunk or the WORKTREE-rooted scoped source
(session-eval) — the un-merged in-session reading lives only in the branch's worktree, while no node
timeline, session model, or other scenario history enters the browser. The strip shows only when a scenario has more than one
reading. New readings arrive only from the eval seam's CLI (spex eval add, eval-core); the pane
never mutates the history.
One reply primitive — a REMARK on the eval's own (node, scenario) thread. A scenario-scoped concern is
never an issue (I1). The thread renders under the workspace as the page's activity — the SAME shared
Thread.jsx the issue detail uses — and its track is the thread's rid-carrying replies, never the
system-minted container stub. A remark is anchored by the ▶m:ss · <step> first-line convention: the
renderer linkifies it (click seeks), and over a clip the composer gains the ⏱ stamp. An open remark
renders prominent, a resolved one settled (remark-teeth); the bit is writable in place at CLI parity
(remark-substrate LAW L — resolve on an agent's remark, retract on one's own; a refusal surfaces its
server message). The composer is issues-view's ONE shared thread composer (quiet bordered container,
two-line floor, auto-grow, persistent action row with the @/**// triggers and icon-only Send),
docked sticky at the main column's foot — the thread scrolls behind it, so circle→remark→circle never
buries the writer. It authors through the CLI-parity /api/remarks (find-or-create by (node, scenario),
author 'human', L: no dashboard-only write); an @session remains a passive [[mentions** reference; a leading
/ opens the review-track command menu (review-commands — /ok as the ONE dashboard door to the
human-ok sign-off, presets as prefills). A fresh scenario shows an empty track with a live composer.
An anchored mark carries its moment's frame — whichever gesture made it. The three mark gestures (a
drag-circle on the paused frame, ⏱, the keyboard's a) are ONE act: grab the current frame to the blob
store (the circle burns its rect in), anchor a remark carrying the ▶m:ss · step line, the frame as a
 link (the hash becoming the remark's typed evidence[]), and — when the
step's owning node differs — a **node** routing line. A re-stamp replaces the anchor line AND its riding
frame together; a failed capture degrades to the text-only anchor, never a blocked mark. A mark is
thereafter an ordinary reply — replyable and @-able: circle + @session inspect this is a timestamped,
framed reference in the stored discussion, never a hidden dispatch.
READ-side on readings — it files none. Verdicts render but are never authored here: readings are filed
by agents through the eval seam WITH evidence; a human pass/fail click would mint a blind manual@1
hand-vote, ruled useless. The human's judgment speaks through the remark composer (an open remark ages the
scenario, remark-teeth). The thread rides ONE server-side overlay folded in as entry.thread
(eval-issue-split) — never re-matched client-side against a resident issues list. Freshness changes
what the loss signal says, never what the human can do — the composer never consults it.