annotator¶
The issues page's eval DETAIL pane — a selected reading full-height (a video plays with a clickable step ruler; the human scrubs, circles, comments; images/transcripts render whole). There is ONE annotation primitive — a time-anchored comment on the eval's own Issue thread — and the verdict stays a separate reading.
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. The annotator is an authoring surface over an already-captured clip; yatsu still runs nothing, and no new ledger structure exists for its sake. An annotation and a comment were once two half-channels — a mark carried a timestamp but couldn't be replied to or dispatched; a comment could converse but had no time anchor. They are one primitive: a time-anchored comment on the eval's own thread.
expanded spec¶
The annotator IS the issues page's detail pane for a selected eval (issues-view's master-detail —
no modal, no box-in-a-box: the reading gets the pane's full height, and switching selection resets the
working state to the new reading). Every evidence kind renders here — an image full-width and
click-to-enlarge (a click opens the same blob in a viewport-size lightbox; click anywhere or Esc
closes, the Esc swallowed so the page's own Esc stack never fires — a screenshot's detail is the
evidence, and the pane's width is not its ceiling), a transcript as
text, a missing blob as the honest sentinel, a blob-less (note) reading its verdict note as the text
body (never an empty media box) — and a video reading plays its clip. When the reading
carries a step-timeline
sidecar, a step ruler renders under the scrubber — click a step to seek to its tMs; an annotation at
moment T names its step by the last-boundary-≤T lookup, and a step's optional owning-node routes the
finding to the node it actually belongs to. Without a sidecar the annotator is a plain player with
comments — degraded gracefully, never blocked.
One annotation primitive — a time-anchored comment on the eval's thread. Discussion and annotation are
the same act: the pane renders the eval's comment thread (issues-view's shared Thread), and every
mark is a comment on it. A comment is anchored by a prose convention — the same philosophy as Spec:
and **node** — a body whose first line reads ▶m:ss · <step> IS anchored to that video moment: the
renderer linkifies it (click seeks the clip), and the composer over a clip gains a ⏱ affordance that
stamps the current frame (its time + the ≤T step name from the timeline). The reply stays plain
{ by, at, body } — no schema grows, and a raw reader still sees the ▶m:ss line. Sorted by their anchor,
the anchored comments read as a review track over the clip (the Frame.io/YouTube-time-comment shape),
but the track is a unified Issue — drainable, assignable, cross-store.
A circle is a comment with a frame. Drag-circling a region on the paused frame captures that frame to
the blob store (the rect burned in) and prefills an anchored comment carrying it: the ▶m:ss · step
line, the frame as a  image link in the body, and — when the step's
owning node differs — a **node** routing line. The frame's hash, derived from that body link, is the
comment's typed evidence[] on the thread; the body is the one raw-readable source. A mark is thereafter
an ordinary reply — replyable, @-able: circle + @new fix this is a timestamped, framed assign, the
anchor riding into the dispatched worker's prompt verbatim.
The verdict stays a reading. The conclusion (pass/fail + a note) is a manual@1 reading filed through
the eval seam's write half (filing.ts, yatsu-core); it no longer duplicates the marks into a frozen
transcript — the annotation track lives on the thread, and the reading records only the verdict. A finding
belonging to another node is already handled: it is an anchored comment routing to that node's thread
(video-evidence's routing; the marks are the prose body, the clip and step-map among the thread's
evidence[]). The annotator invents no verdict states, no timeline tables, no locks.
The thread rides the Issue mechanism — an eval's comment thread IS a local Issue. It is deterministically
bound to this (node, scenario) by its concern key — eval: <node> · <scenario> — matched by concern TEXT
against the page's resident issues list (ids de-collide, concerns don't). No thread exists until someone
speaks: the first comment lazily creates it through the SAME propose the CLI uses (nodes: [node], the
comment as the body), every later comment replies through the SAME reply — one thread per pair, forever,
whatever its status. The reply list and composer are the SAME shared thread UI the issue detail uses
(issues-view's Thread.jsx — one thread UI, three homes: local issue, forge issue, eval), so an
@session/@new typed in a comment dispatches (mentions), and the thread lists in the issues group
like any local issue with its node chip focusing the graph. Where no resident issues list is wired in (the
session eval tab), the section does not render — a blind post would mint duplicate threads, so there the
clip is a plain player with the verdict only.