AF2 and stage-review remaining delivery¶
The minimum usable release completes the existing branch tabs, restores AF1 action/category behaviour, and corrects controls and navigation in the redesigned workspace. It preserves saved answers, incomplete drafts, permissions, and the legacy rollback path. Personalisation follows in independently shipped slices; it remains required programme scope.
Sequence and observable acceptance¶
| Slice | Dependencies | Acceptance and evidence |
|---|---|---|
| Branch tabs, #2810 | Merged isolation repair #3207 | Current-head AF2/stage-review tests, review and CI pass; switching tabs preserves drafts and control identity; nested errors reveal ancestors; keyboard navigation and deletion preserve focus and selection. |
| Status cues and action/category/order parity, #3084 | #2810 | Distinct shapes identify all branch states without colour; invalid forms cannot Complete; dirty partial work can Save subject to permission and saving guards; category icons and visited validity match AF1; adding/deleting/reloading units preserves identity and creation order. |
| Controls and comments, 3A | #3084 | Every supported control presents prompt, helper, input and errors in that order; long prompts wrap; accessible names and descriptions stay associated; comment add/edit/cancel/save and quote insertion retain their semantics without hover/focus layout shifts. Unit regressions plus gallery browser screenshots. |
| Panels and navigation, 3B | #3084; PDF-host owner integration | Header buttons have unclipped focus surfaces; entity dialog Escape/backdrop/close preserve drafts and restore focus; My Studies design footnote removed while project permission and dirty navigation guards remain; Skip occupies a consistent header location across all stage types with unchanged behaviour. |
| Release baseline, 4 | 3A, 3B | Annotation-only, screening-only, combined; narrow/wide; light/dark; pristine/dirty/invalid/completed; navigator on/off. Include nested branches, many entities, quote-to-comment and save failure. Required final-head CI and preview/staging journeys pass; deployed versions verified; user guide and rollback recorded. |
| Entity reordering, 5A | Baseline; review-data ordering contract | Drag and keyboard move actions persist order without changing entity identity, labels, answers or completion; reload, deletion, selection, failure and read-only paths covered. |
| Reviewer layouts, 5B | Baseline; reviewed interaction and storage contract | Per-reviewer versioned layouts for each capability combination; movable Source/Screening/Annotation panels; usable narrow-screen fallback, reset, schema/capability migration and failed persistence recovery. |
| Source modes, 5C | 5B; PDF viewer lifecycle/host integration | Embedded, floating and detached modes share authoritative study selection and preserve quotes; keyboard movement, viewport recovery, blocked-popup fallback, window close, study change and sign-out verified. |
| Backlog and cleanup, 6 | Stable accepted default rollout | Reconcile every #3067 entry with evidence or explicit follow-up; >8 branches usable; remove only unreferenced legacy components after rollback is no longer needed; preserve the original untracked notes and commit completion evidence. |
The shortest critical path is #2810 → #3084 → 3A/3B → baseline acceptance. Shared shell files
must be integrated after the active PDF-host changes; independent form work can proceed in
parallel. Every implementation uses a sole-writer worktree under the repository’s .worktrees/ directory.
Flags and release gates¶
Existing annotationFormV2 and stageReviewRedesign gates remain in force. No production
activation is implied by merging implementation. Wider/default enablement requires acceptance
and a release decision, followed by a stability interval before legacy removal.
Entity order belongs to each reviewer’s review and must persist in that review’s database data. It must preserve entity names, identity and answer ownership. Preserve record-and-stay versus advance behaviour pending its separate decision. Reviewer-layout storage and mode/position persistence need a concrete reviewed contract. Credentials and production activation remain operator-controlled.
Infrastructure hardening, preview race repairs and unrelated colour/selector defects are only baseline blockers when current evidence proves an intrinsic correctness, regression or major security issue. Coordinate existing repair owners rather than duplicating them.
Recovery evidence¶
On 2026-09-05, #2810 remained open at 7d20971d1 with failed historical Angular checks;
3084 remained stacked on it at 932cba73c3. #3207 and #3174 are merged. Both branches merge¶
cleanly into the refreshed stack locally. Original pr/ worktrees have untracked status notes
and are preserved. The main checkout also has unrelated staged/unstaged work; validation uses
committed code in isolated worktrees.
Sources and implementation contracts¶
- AF2 delivery overview and feature brief.
- Annotation question extensibility decisions and delivery map.
- Accelerated stage-review requirements.
- Remaining-work tracking issue #3067.
- Reviewer-specific entity ordering, #3224.
- Reviewer layout persistence, #3225 and Study Source modes, #3230.
The acceptance criteria above govern delivery; prototypes illustrate interactions and do not replace these requirements.
Comment editing contract¶
Saved comment text appears in a compact chip with a Material icon-button edit action, retaining its expanded touch target and hover feedback. Add/Edit opens a local comment transaction held in the shared form store, so pagination and the narrowed Focus view do not lose it. Cancel drops that transaction; Save comment applies it to the review draft and closes the editor. Neither action writes to the server. Pending text participates in the existing unsaved-navigation guard.
Whole-session Save progress and Complete apply all pending comment edits before validation and submission. Revert, deletion and replacement by another session clear the relevant local editor state. Quotes inserted while editing remain in both the editor and its Cancel baseline: cancelling typed changes must not erase an independently inserted quote. Read-only sessions show comment text without edit controls.
Focus contract (revision 2 §6.7)¶
Focus is one feature with two entry scopes, and it is a state of the Annotation panel, never a dialog. The panel header stays with a "Focus" hint; everything else collapses to the 48px Focus bar, and the questions take the panel's full width. It works identically when the panel is grouped, maximised, floated, or opened in its own window, because it makes no assumption about its host.
- Shown-set Focus is entered from the category line's Focus button, offered only while
at least one entity is shown. It stacks every shown entity as a sticky-mini-header
section. Previous/next scroll between those mini-headers — the current one is
determined by scroll position and named beside the chevrons — and never swap entities.
A mini-header's
filter_center_focusnarrows Focus to that entity; its × hides the entity and Focus continues with one fewer. When none remain, an information strip explains how to pick entities; Focus does not close itself. The bar's ⋮ is the "Shown in Focus" menu: a checkbox item per entity that keeps the menu open, Show all, and Add. - Single-entity Focus is the shortcut entered from a section mini-header, an entity card's ⋮, or the entity menu. It shows that entity alone, previous/next move between entities, and the ⋮ is the entity menu with "Shown in Focus…" last.
- Study uses the same Focus state: the same shared draft, the same Save and Complete,
the same Back and Escape. Study is a singleton implicit context, so it carries no
entity-set controls — no previous/next, no position, no entity menu — and no invented
second entity. It does reach the category boundary, because continuing to the next
category is navigation rather than an entity-set control: Study's single section is
always the end of its questions, so the pill and the end-of-questions footer offer
"Continue to
". - The bar's contents degrade by its own measured width (a
ResizeObserveron the bar, not viewport breakpoints), dropping the position, then the category name, then the Unsaved chip, then the action labels, and finally ellipsising the entity name. Back, the entity icon and name, previous/next and the ⋮ are never removed, and everything dropped stays reachable in the ⋮ header line and the breadcrumb tooltip. - At the last entity the right chevron becomes the next-category pill and an end-of-questions footer repeats the move. An empty next category lands on its empty state inside Focus. At the very end of the annotation the pill reads "Finish".
- Leaving Focus never discards anything. The same question forms stay mounted as the same component instances across entering and leaving, because Focus only changes which units the one workspace projection returns and the class on their wrapper. There is no overlay container, so there is nothing to move and nothing to re-render.
- Bounded rendering is unchanged: shown-set Focus mounts the shown set intersected with the current card page, so Focus pages through the stack under the same cap the workspace uses.
- The top annotation action bar is hidden while Focus is on. The Focus bar carries Save and Complete itself, under the same anchors and the same enable rules, and both surfaces call the same commands — there is one authoritative draft and one save path.
- Escape leaves Focus first in the Escape cascade and claims the event, so the host's Maximise and Rearrange handling does not fire on the same key press. Back and Escape both return keyboard focus to the control Focus was entered from.
- The panel header stays in Focus and carries a "Focus" hint, but that header belongs to
the stage-review Dockview shell, not to the form. AF2 therefore publishes its Focus state
through a
focusStateChangedoutput —{scope, category, label}, or null when Focus is off — and the shell renders the hint from it. This is deliberately a description and not a handle: a host can draw a hint and nothing else. No host can enter or leave Focus, because that would make the panel chrome a second owner of what is mounted.
This supersedes the per-entity fullscreen dialog, including the Study fullscreen dialog proposed in #3394.
Branch-answer overflow preferences¶
The 2026-09-08 reviewer mockup selects wrapping quiet branch tabs (15a) as the
default at every nesting level. This first slice changes layout only: existing
status shapes, active underline, branch identity, selection and draft state remain
shared and unchanged. Long labels fit the available width and retain their full
accessible names. Flag decision: this is inside the existing annotationFormV2
feature; no separate flag is needed for the default tab layout correction.
The account-persisted reviewer preference is now delivered. Revision-2 §5's three
illustrated radio cards write overflowMode (wrap | scroll | menu, default
wrap) to ReviewerWorkspaceSettings; the review page hands it to the annotation
form through the ANNOTATION_ROW_OVERFLOW injection token, and both rows the
design names honour it — the protocol branch tabs and the §6.3 entity cards. They
are render choices over the same branch data and status logic, not project-admin
settings and not the Dockview panel tabs.
| Mode | Branch tabs and entity cards |
|---|---|
wrap |
Every control stays on screen, wrapping onto more rows. The default, and the behaviour of the first slice. |
scroll |
One row, horizontally scrollable, with a chevron button at each end that disables when there is nothing left that way. overflow-y is pinned to hidden so Material's -1px ink-bar margin cannot spawn a vertical scroller. |
menu |
As many controls as fit, then a +N more menu holding the rest. The row is measured with a ResizeObserver plus a MutationObserver, so the count follows both the row's width and the controls in it. |
Switching modes preserves selection, drafts, focus, error reveal and accessibility.
Collapsed controls keep their space in the layout and are hidden by visibility,
which takes them out of the tab order and the accessibility tree; the +N more
menu is what puts them back, with the branch tabs' selection semantics and the
entity cards' shown/hidden checkbox semantics and reordering intact. Arrow-key
navigation walks the tabs still on the row rather than stepping into a collapsed
one that cannot take focus. Wrapping chips (15b) are an unselected alternative.
The mockup references an unspecified 10a selector beyond approximately15
branches. Neither that selector nor an exact cutover is supplied; the older >8
note in repository docs is not a settled threshold. Obtain that design before
implementing the cutover. Do not conflate it with the existing ancestry selector.
Status: all three modes implemented for both rows, driven by the persisted
preference. Browser coverage checks 375px nested wrapping, visible status
indicators, stable tab IDs, keyboard selection and preserved nested drafts;
preview acceptance remains pending. Flag decision: no new flag — every mode is
inside the existing annotationFormV2 feature, and the preferences dialog that
sets the choice is inside the existing default-off stageReviewRedesign flag.
Hosts with no reviewer (annotation preview, reconcile) keep the wrap default.
The unresolved 10a selector cutover below is still outstanding.
The branch-wrapping MVP keeps Add on the same row when short tabs leave room; longer sibling lists wrap without losing their status markers. The browser contract checks both a short row and actual multiple-row geometry at 375px.
Tab-strip hairline and label clipping (confirmed 2026-09-08)¶
Two review questions on the wrapping default were settled by measurement in a headless browser rather than by preference:
- The hairline underlines the tablist, not the whole strip. It sits on
.branch-tabsand stops at the tablist's right edge, so it does not run under Add when Add shares the row. The alternatives are worse: the strip wraps, so a border on.branch-tab-striplands under the last row only -- the Add row once Add drops below the tabs, leaving the tabs unmarked. Forcing a single row withflex-wrap: nowraprestores a full-width hairline but was rejected: below roughly 300px theflex: 1 1 autotablist collapses to 0px and every branch tab disappears, defeating the point of wrapping. - Tab labels clip without an extra
min-width: 0.overflow: hiddenon.branch-tab-textalready zeroes the flex item's automatic minimum size (CSS Sizing: the content-based minimum is 0 when overflow is notvisible), so the label shrinks inside the capped tab and ellipsises. Measured at 320px: a 452px label inside a 270px content box, clipped..branch-add spanworks by the same rule, not because ofmin-width: 0on its parent.
Regression coverage for the tab-label clipping case is tracked as a follow-up; the browser spec currently asserts clipping for the Add label only.
Review navigation waits for the current screening write to settle: Skip/Next and My Studies cannot leave while a decision is in flight, and only the matching intent, study, stage and reviewer outcome releases that guard. Study-keyed cached decisions are accepted only for the current stage and reviewer. Releasing a screened study's reservation uses the annotation-statistics transaction and its durable tracking-mode agreement, keeping fresh projections consistent with the source study.
Validation fixture cleanup¶
Consolidate the small duplicated valid-layout builders in API/controller and Mongo repository tests as a maintenance follow-up after workspace acceptance. The independently tested serialization boundaries remain covered; sharing those fixtures is not a runtime correctness requirement for the current delivery. The same follow-up should introduce a shared valid/invalid JSON corpus for the client and API validators; current boundary cases have matching focused tests.