Reviewer workspace layouts — minimal delivery contract¶
This is the reviewable contract for Slice 5B, based on shell commit 484d50f99. The coordinating task approved this contract on 5 September 2026. Implementation is now prepared for current-head review. The sibling interactive prototype demonstrates panel order, split/stacked arrangement, keyboard move controls, narrow fallback and reset. Its Save button deliberately does not claim durable persistence.
Usable boundary and shortest path¶
The reviewer can reorder Study source, Screening and Annotation, select Split or Stacked, explicitly save a layout for their account, reload it on another device, and reset the current stage type. Only panels supported by the stage appear. This remains inside the existing default-off stageReviewRedesign branch. No AF2 code, study navigation semantics, source-window modes, panel resizing or new grid dependency is required.
Critical path: approve this interaction/storage contract; implement authenticated settings read/write and database tests; generate the API client; implement a pure layout model and settings state; integrate stable panel views with CDK drag and keyboard actions; verify preservation and failure journeys; review and ship after the shell PR. Default enablement remains a separate release decision.
Interaction and rendering¶
- A Layout toolbar directly below the shared header opens compact controls. Select Split or Stacked and move each available panel earlier/later using explicitly labelled buttons. Move controls disable at boundaries. Pointer drag uses a visible drag handle; ordinary selection and text/quote interaction do not start dragging.
- One ordered panel list determines both DOM and visual order. CDK drag moves the same keyed views as keyboard actions. Moving a panel, changing arrangement and reset must preserve its Angular component instance, unsaved annotation values, touched state, selection and quote host.
- Split on wide screens places the first panel in the first column, spanning the remaining panel rows; subsequent panels stack in the second column. Source-first is the default. Columns have minimum widths of approximately 320 and 420 pixels, with available-width fallbacks. Stacked uses one column.
- Below the existing 980px shell breakpoint all panels use one column in their chosen order. This fallback never overwrites the saved desktop arrangement. Existing Study source disclosure behavior remains.
- Save layout persists only presentation preferences and reports Saving, Saved or an actionable failure. It never saves or completes a review. Reset changes this stage type to defaults locally; the reviewer uses Save layout to make that reset durable.
- A polite status announcement states the panel and its new position. Keyboard movement keeps focus on the moved panel's control. Closing layout controls restores focus to Layout. Layout controls are independent of annotation dirty state because no form is destroyed.
Version 1 payload and capability identity¶
Fixed capability keys are screening, annotation, and combined. Their default panel orders are respectively [source, screening], [source, annotation], and [source, screening, annotation]. The key derives from authoritative stage capability selectors. No stage/project ID or investigator ID is part of the save body. Layouts are reviewer-wide and independent of project permissions or review data.
Each key is saved separately. Moving between capability combinations loads the corresponding key and leaves the other two intact. If a version-1 record contains a formerly available panel, normalization drops it; newly required panels append in default order. Duplicate or unknown panel IDs, malformed values, unknown versions and invalid arrangements fall back to defaults with a recoverable notice, without silently overwriting the stored record. API writes accept only the exact supported panel set for their key and reject invalid requests with 400.
Proposed API and database boundary — review before implementation¶
Use an authenticated current-user-only controller at api/account/review-layouts: GET returns the three optional layouts; PUT /{capability} validates and saves one version-1 layout. Controller ownership comes exclusively from ICurrentUserService.GetUserId(). Missing identity returns 401; an absent or deactivated Investigator cannot write. There is no route/body parameter for choosing another investigator. The existing authorization middleware and generated API authentication apply.
Store one small ReviewerWorkspaceSettings document per Investigator ID in its own Mongo collection, with at most the three allowed layout keys. Update one layout using $set and upsert; never load and replace the document. Save responses return the accepted layout after the write succeeds. Writes to different capability keys cannot overwrite one another; explicit saves to the same key use documented last-successful-write-wins semantics. No broad profile DTO/settings editor is introduced.
Reason for a separate collection: current AccountController profile updates call unit-of-work aggregate saves, and MongoUnitOfWorkBase uses replacement writes. Adding a field to Investigator and writing it atomically does not alone protect it from a concurrent profile replacement using an older aggregate. A separate narrow repository avoids changing profile-write semantics and prevents either feature from overwriting the other. Register this repository directly with the API's existing data DI, rather than adding a presentation preference to every IPmUnitOfWork constructor/mock.
Loading and failure behavior¶
Settings load only for the redesigned shell. Until the read settles the existing default workspace remains usable, with layout-edit controls disabled to prevent late data from overwriting a local arrangement. Read failure shows Retry and Use defaults; choosing defaults enables local customization but does not claim persistence. An explicit subsequent successful Save may replace the current key.
A failed Save retains the local arrangement, unsaved-layout status, all annotation edits and a Retry action. No silent localStorage fallback presents itself as a database save. A successful Save updates the baseline only for that request's account/capability and payload; newer local changes stay marked unsaved. Requests are cancelled/ignored on account or capability changes so stale reads and writes cannot apply another user's or stage type's layout. Save is disabled while a write is running; subsequent edits remain local and can be saved after it finishes.
Observable acceptance and verification¶
| Item | Evidence required |
|---|---|
| Account isolation | Endpoint tests establish authorized self identity; missing identity and absent/deactivated account deny write; request cannot select another user. |
| Durable bounded settings | Mongo integration saves and reloads each key; two concurrent distinct-key updates survive; profile changes leave settings intact; invalid capability/payload rejected. |
| Version/capability safety | Pure-model tests cover all 3 combinations, narrow fallback, duplicates/unknown IDs, unsupported versions and capability normalization. |
| Stable workspace | Integration verifies source/screening/annotation view identities and unsaved form state survive reorder, arrangement and reset; quote host still points at the source panel. |
| Accessible movement | Keyboard and pointer reorder produce identical DOM order; boundary buttons disable; focus and live announcement follow moved panel. |
| Failure recovery | Loading failure/retry/default choice; save failure/retry; stale result after capability/account change; edits during a save; reset affects only current key. |
| Existing behavior | Current stage-review and required AF2 integration regressions pass; legacy/default-off path unchanged; current-head API/backend checks and generated-client checks pass. |
| Browser acceptance | Wide/narrow screenshots and keyboard/drag journeys for each stage combination; verify real form preservation and no quote-selection interference. |
Ordered follow-ups outside this slice¶
Resizable columns and arbitrary grid sizing; detached/floating Study source modes (Slice 5C); named layouts or sharing; conflict UI for simultaneous same-key saves; wider/default rollout. These do not prevent a usable, saved reviewer layout in this slice.
Implementation evidence¶
- Current-investigator API and separate settings repository are implemented. Both read and write resolve the active account with an uncached repository query; no request-selected identity is accepted.
- API tests: 12 passing authorization, validation and persistence-failure cases. Mongo integration: 3 passing cases covering concurrent first saves, capability and reviewer isolation, and profile-replacement preservation.
- Stage-review regression suite: 362 passing tests across 22 files. Includes real Angular source/form stubs, quote-host identity, unsaved draft preservation across reorder/stack/reset, current capability persistence, loading/saving failures, stale account/capability results and version fallback.
- API Release build generated OpenAPI, TypeScript client and checksums through the repository generator. The existing redesign flag remains default-off.
- Remaining release verification: current-head review/required checks and browser acceptance of pointer drag, keyboard movement, narrow/wide layout and real AF2 behavior in preview/staging. No wider rollout is performed by this implementation.
Slice 5C compatibility handoff¶
Study source stays in panelOrder even when a later source-mode feature floats or detaches its content. Add source presentation preferences in this separate settings contract, not to Investigator. A version-1 PUT replaces one complete capability layout, so Slice 5C must explicitly preserve its added fields when handling old version-1 clients (or introduce an appropriately migrated versioned endpoint). Do not silently allow a legacy layout save to erase source-mode preferences.
Slice 5C source modes¶
Source mode and floating bounds are additive optional fields in the version-1 layout. Missing values normalize to embedded mode and a 520×640 panel at (24,24). Server updates individual fields atomically, preserving source preferences when an older client saves only arrangement/order. Explicit reset sends embedded mode and default bounds. No source contents or study IDs enter reviewer settings.
Embedded and floating use the same mounted source template; annotation instances never move into a popup. Floating supports a dedicated pointer handle, arrow-key movement (Shift for larger steps), numeric width/height controls and Escape/Return to layout. Actual viewport bounds constrain display after resize or zoom. Below 768px source returns to embedded display without overwriting the desktop preference.
Detached mode opens a source-only application window from a user gesture. The source remains in-page until that window completes an exact-origin, exact-window capability handshake. Saved detached preference offers an explicit reopen control; it never automatically opens a window. Blocked, closed or navigated windows restore the in-page source. Review destruction, authorization loss and reviewer changes revoke the source and close the window. The popup clears its source, quote targets and selection when its opener closes or its document is replaced, including reload or cross-origin navigation. An established window tolerates heartbeat silence during background throttling or device sleep; only the initial parent-side handshake has a deadline.
Detached title/abstract quotes carry a study revision, monotonic request sequence, source kind and registered target ID. The review validates source text containment and rejects stale/replayed/foreign messages; AF2 independently checks its current study, editable state and registered draft before appending and focusing the comment. PDF open/download retains its existing link behavior; the separately owned integrated PDF viewer is unchanged.
Acceptance includes transport rejection and lifecycle tests, persisted-source/legacy-client compatibility, viewport recovery, unchanged mounted source/annotation instances, and current-form quote authorization. Browser verification must exercise mode switching with a dirty draft, keyboard/pointer movement, popup-blocked fallback, popup close, quote insertion, study navigation and narrow-screen recovery before shipping. Default-off rollout remains unchanged.