You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A reviewer who has ten cases carrying two captures each has to open ten cases,
walk each grid, and close it. The carousel — the one screen where judging
happens — only ever knows the case it was opened from: its navigation is
derived from that case's grid (apps/web/src/widgets/capture-carousel/CaptureCarousel.vue:104),
and its address embeds the case (apps/web/src/app/router.ts:29). On a project
that re-captures broadly, the reviewer spends more time navigating the
catalogue than looking at pixels.
The evidence is already there and already stored: a case's state is stored
precisely so it can be filtered without scanning (docs/design/product.md §3.1),
and every capture carries its own status and freshness (§3.1, §3.3). Nothing
walks that set as one list.
The model, decided
The queue is contextual. It covers the category on screen and everything
beneath it. Opened at the project root that is the whole project; opened three
levels down it is those three levels' worth. The catalogue is one screen at
every depth (apps/web/src/app/router.ts:25-26), so a queue scoped to the
project would advertise 42 on a category holding 12 — a number that lies.
One rule at every depth, the project root being the case where nothing is
excluded.
Two statuses enter it: to-review and moved — the whole of what awaits
the reviewer, said in the vocabulary derive the statuses and give cases and captures one vocabulary #192 settles. A delivered fix has no
status of its own: it reads to-review like any capture awaiting a verdict,
and what distinguishes it lives on its comment.
Grouped by case, then by step order. A reviewer finishes a case before
moving to the next: the flow is the unit of meaning, and jumping between
contexts to empty a backlog by age wastes more than it saves.
The interface says capture, never square.square and cell are grid
vocabulary from the code; docs/design/product.md §2 names the thing a capture. Nothing user-facing says otherwise.
No marker in the case banner. A capture has one status and the carousel
already shows it: the empty verdict pair for to-review, the stage badge for moved. A third word for the same fact is what derive the statuses and give cases and captures one vocabulary #192 exists to remove. The
banner carries the category trail and the case name, nothing else.
No transition marker when the walk crosses into the next case. The case
name changes in the banner, at title size — annotating a change already on
screen is the same defect one size down. If crossing proves easy to miss in
real use, the answer will be a movement of the banner, decided on that
observation and not before.
The carousel is not duplicated. It stays one component. What changes is
where its navigation comes from: today it derives left/right from the grid it
was handed, tomorrow it walks a queue it is handed. A second carousel would
fork the densest verdict logic in the client (accept, unaccept, refuse,
unrefuse, judge, unjudge, draft editing, keyboard) and the fork would drift.
The queue is a read, never a state. It is computed from stored statuses
at request time; no endpoint accepts it, nothing stores it
(docs/adr/0002-server-owns-the-review-lifecycle.md).
Sub-issues (checklist)
write the review queue into the product design #203 — Write the queue into docs/design/product.md: what enters it, in
what order, and what leaving it means. Design first — the product doc is
silent on cross-case review, and code must not decide it.
This epic reads moved as a capture status and the case as refused / accepted. Built before #192 lands, the queue would have to read freshness
beside the status and the screen would say two things at once. #193 first, then #194, then #195, then this.
Known tension, to be resolved in the design sub-issue
A case is meant to be held exclusively by its reviewer for the duration of the
session (docs/adr/0005-exclusive-case-locking.md). Locking is not
implemented today — no endpoint, no column, and #95 is still open — so nothing
blocks this work now. But a queue walks N cases in one sitting, and the design
must say what a queue does with a case somebody else holds: skip it, show it
read-only, or claim and release as it walks. Deciding that here, on paper, is
cheaper than discovering it when locks land.
Out of scope
Filtering beyond the category scope — by reviewer, by age, by axis value.
Any change to how a verdict is written: the carousel keeps sending each
verdict as it is made, per case (apps/web/src/features/review/useReview.ts:11).
Reordering, prioritising or assigning a queue between several reviewers.
Why
A reviewer who has ten cases carrying two captures each has to open ten cases,
walk each grid, and close it. The carousel — the one screen where judging
happens — only ever knows the case it was opened from: its navigation is
derived from that case's grid (
apps/web/src/widgets/capture-carousel/CaptureCarousel.vue:104),and its address embeds the case (
apps/web/src/app/router.ts:29). On a projectthat re-captures broadly, the reviewer spends more time navigating the
catalogue than looking at pixels.
The evidence is already there and already stored: a case's state is stored
precisely so it can be filtered without scanning (
docs/design/product.md§3.1),and every capture carries its own status and freshness (§3.1, §3.3). Nothing
walks that set as one list.
The model, decided
beneath it. Opened at the project root that is the whole project; opened three
levels down it is those three levels' worth. The catalogue is one screen at
every depth (
apps/web/src/app/router.ts:25-26), so a queue scoped to theproject would advertise 42 on a category holding 12 — a number that lies.
One rule at every depth, the project root being the case where nothing is
excluded.
to-reviewandmoved— the whole of what awaitsthe reviewer, said in the vocabulary derive the statuses and give cases and captures one vocabulary #192 settles. A delivered fix has no
status of its own: it reads
to-reviewlike any capture awaiting a verdict,and what distinguishes it lives on its comment.
moving to the next: the flow is the unit of meaning, and jumping between
contexts to empty a backlog by age wastes more than it saves.
squareandcellare gridvocabulary from the code;
docs/design/product.md§2 names the thing acapture. Nothing user-facing says otherwise.
already shows it: the empty verdict pair for
to-review, the stage badge formoved. A third word for the same fact is what derive the statuses and give cases and captures one vocabulary #192 exists to remove. Thebanner carries the category trail and the case name, nothing else.
name changes in the banner, at title size — annotating a change already on
screen is the same defect one size down. If crossing proves easy to miss in
real use, the answer will be a movement of the banner, decided on that
observation and not before.
where its navigation comes from: today it derives left/right from the grid it
was handed, tomorrow it walks a queue it is handed. A second carousel would
fork the densest verdict logic in the client (accept, unaccept, refuse,
unrefuse, judge, unjudge, draft editing, keyboard) and the fork would drift.
at request time; no endpoint accepts it, nothing stores it
(
docs/adr/0002-server-owns-the-review-lifecycle.md).Sub-issues (checklist)
docs/design/product.md: what enters it, inwhat order, and what leaving it means. Design first — the product doc is
silent on cross-case review, and code must not decide it.
under a given category, the whole project when none is given, ordered as
decided.
opened from the catalogue. Mockup validated 2026-09-08.
Depends on #192
This epic reads
movedas a capture status and the case asrefused/accepted. Built before #192 lands, the queue would have to readfreshnessbeside the status and the screen would say two things at once. #193 first, then
#194, then #195, then this.
Known tension, to be resolved in the design sub-issue
A case is meant to be held exclusively by its reviewer for the duration of the
session (
docs/adr/0005-exclusive-case-locking.md). Locking is notimplemented today — no endpoint, no column, and #95 is still open — so nothing
blocks this work now. But a queue walks N cases in one sitting, and the design
must say what a queue does with a case somebody else holds: skip it, show it
read-only, or claim and release as it walks. Deciding that here, on paper, is
cheaper than discovering it when locks land.
Out of scope
verdict as it is made, per case (
apps/web/src/features/review/useReview.ts:11).