Part of the approved Iteration 2 design pack. Read together with design-decision.md, which records binding corrections and the O1–O5 dispositions.
Study management — decision log (Waves 3–5 handoff)¶
Grounded in source-truth.md (camaradesuk/syrf@main, cc271f478d03, read 2026-09-01).
Status: Locked = build as specified · Wave 3 = specified, needs implementation decision detail · Open = needs product owner.
Structure and navigation¶
- D1 · Three linked routes (Library / Searches / Processing) under one "Study management" sidenav group; below the wide breakpoint the three destinations use merged PR #2994's adaptive primitive (persistent rail ↔ sticky route-aware compact selector, content-driven breakpoint) — not a bespoke segmented-tabs variant (binding correction 3 in design-decision.md). Spine from Iteration 1 option A. Locked
- D2 · Processing is read-only history. Commands stay with the owning page (upload/retry on Searches, bulk update on Library). Only Bulk PDF exposes cancel — the only family with a cancel/retry API. No cancel/retry is ever rendered for the other three families even in error states. Locked
- D3 · Contextual summaries + deep links. Library and Searches show each relevant operation as a status strip with "View in Processing"; the deep link reveals the operation on Processing even when terminal and outside the default filter, with an info notice ("Showing the linked operation…") and a Clear action. Locked
- D4 · Default filter = Active + Needs attention; "All operations" paginates completed history 25 rows at a time with an honest remaining count. Locked
Status language (shared StatusView contract)¶
- D5 · One shared status-chip component across all four families, adopting the shipped Bulk PDF StatusView contract (kind: ok/stopped/warn/muted/info; honest bar modes; ariaValueText). Replaces Iteration 1's raw-hex dot/triangle chips. Locked
- D6 · Deployed labels are reused verbatim where a deployed map exists (Search Import statusText, RoB statusMap, Bulk PDF status-presentation). Bulk Study Update has no deployed map outside the dialog → sentence-cased enum ("Validating on server") with raw enum + value in row detail. Locked
- D7 · Unknown status values render "Unknown status (17)" with the raw value in detail — a deliberate divergence from Bulk PDF's "Status unavailable" fallback because the numeric value is diagnostic. Unknown never maps to Running. Reconcile in Wave 4 with the Bulk PDF owner (the fallback lives in Wave 4's
project-admin/bulk-pdf-upload/**ledger). - D8 · Honest progress only. Determinate bars require a real numerator/denominator (e.g. verifiedPartCount/partCount); otherwise indeterminate with "progress not yet measurable" and aria "Working — no estimate available". Completion reports StudyUpdateParseResult matched/modified, never parsed-row counts; a 0-changed run says so explicitly. Locked
- D9 · Missing data is named, not faked. Timestamps/initiators absent from DTOs (Search Import has LastModified only; RoB has no initiator; initiator can vanish for non-public members) render "Not available". Locked — the Bulk Study Update lastModified convention-mapping bug (year-one risk) is a Wave 1A backend fix (landed in PR #3052), not Wave 3 frontend work.
Processing table mechanics¶
- D10 · Grouping: Needs attention (pinned, warning-tinted) → Active → Completed and other outcomes. Group headings carry counts. Locked
- D11 · Six-column grid that compresses, not a fixed-width scroller. As shipped (
processing-group.component.scss,processing-row.component.scss):--processing-columns: minmax(80px, 118px) minmax(140px, 1fr) minmax(240px, 380px) minmax(112px, 128px) minmax(88px, 118px) minmax(48px, 96px)— Type / Operation / Status and progress (Status's original 170 and Progress's original 210 merged into one cell) / Updated / Started by / link — with a labelled column header row; chips constrained to their own cell so variable label lengths cannot drift columns. Each column is a range, not a floor: the grid compresses to fit anywhere from the shell's 1200px content cap down to the ~790px the sixminmaxfloors need, so nomin-width: 1060pxscroller is needed across that band. Below$syrf-bp-lt-md(904.98px) the header row hides and each row becomes a card (grid-template-areas, type/operation/status stacked, updated/initiator full-width) — this is the real card threshold, not a TBD. The horizontal scroller (.group__scroller,overflow-x: auto, labelled, focusable, WCAG 1.4.10 table exception) remains only as a fallback for the rare case where the compressed grid still overflows its container; itstabindexstays conditional on actual overflow. Locked — merge rationale for the merged Status/Progress cell unchanged: one chip already carries both the status label and its progress bar/percent as a single unit (D5), so a second, separate progress-bar cell would either duplicate that bar or stand empty for every non-progress state; one cell owns both concerns and the column count and grid stay one word simpler to explain.
(Design review, 2026-09-08/09 — conventions settled across Library/Searches/Processing, recorded here as the canonical statement; see DESIGN-REVIEW-study-management-2026-09-08.md (+ crosscutting/layout detail files) in the maintainer's handover archive for the full audit.)
- "Updated" timestamps use formatBritishDateTime on every surface (Library strip, Searches summary/table, Processing column and detail); the row-detail panel additionally shows the full UTC timestamp (D14), never as the only rendering elsewhere.
- "View in Processing" is rendered as text followed by a trailing arrow_forward icon on every surface, and is a ≥44px touch target at every viewport width, not only below the small breakpoint.
- Unknown status and Bulk PDF Expired both render the muted chip kind (per the token map's chip-kind table above); Expired's Needs-attention grouping (O1) is unchanged — grouping and chip kind are independent.
- The page shell — a centred 1200px container, an h1 on --mat-sys-headline-small, a top-right header action row, and one shared page-state component for loading/empty/error — is the layout convention for all three Study Management destinations (Library, Searches, Processing).
- The Processing lede names the Bulk PDF retention limits (20 uploads per search, 100 per project, running/expired jobs never pruned) instead of only gesturing at pruning.
- Unknown statuses render the muted chip with the sub-line "Refresh the page", so a status the client cannot decode never reads as active and always tells the viewer what to do about it.
- Page-state components own loading/empty/error copy — each page's loading, empty and error states are authored once in the shared app-page-state component rather than re-implemented per page.
- Processing card threshold: $syrf-bp-lt-md (904.98px) — six columns need ~790px of grid, so 905px is the widest viewport where the table still fits the content column (settled by C2b #3439, see D11 above; no longer TBD).
(2026-09-09 — C2a #3434 and C2b #3439 merged: all three Study Management destinations now share app-page-shell — a centred 1200px container, an h1 on --mat-sys-headline-small matching the sidenav labels "Library" / "Searches" / "Processing", and a top-right action row — plus app-page-state for loading/empty/error. Searches is routed directly with no ProjectAdminComponent wrapper and no aliceblue admin-tint ground.)
- D12 · Live updates: 4s poll/push cadence simulated; re-sorting is deferred while a row is expanded ("Order updates paused…" status) so open rows never jump. Reconnecting keeps rows visible and marks them stale. Locked
- D13 · Bounded error lists: first 10 of N with honest head ("first 10 of 150, bounded"), scrollable, "Show all N" toggle. Parse-error kind (e.g. MalformedRecord) carries specifics; the family status stays the deployed "Error". Locked
- D14 · Row detail exposes raw enum + numeric value, full UTC timestamps, initiator, job ID with Copy. Locked
Permissions and states¶
- D15 · Server-filtered aggregate (ProjectDetails) with one loading boundary; the empty state means "none visible to you" and links to the owning pages. Load failure states "Your operations are unaffected" + Retry. Permission gates per family: ViewSearches/ImportSearch/RemoveSearch, BulkUpdateStudies, BulkPdfUpload, ViewStudies. Locked — RemoveSearch joined the Search Import gate (server + client) on 2026-09-07 per the B17 product-owner-approved permission matrix; PR #3053 had shipped only ViewSearches/ImportSearch.
Accessibility (see A11y Spec Board)¶
- D16 · Contract implemented: polite live region per page, role=status notices, progressbar aria-valuetext per StatusView, aria-expanded rows, labelled focusable scroll regions, reduced-motion static indeterminate track. Locked
- D17 · UI-wave additions: forced-colors CSS block, ≥44px touch targets below 600px, and deep-link focus ride Wave 3; the app-shell skip link rides Wave 5 (the sole route/navigation writer owns the shell touch). Wave ⅗
Open questions (Wave 4–5)¶
- O1 · Should Expired Bulk PDF jobs (not terminal — cleanup pending) surface in Active or Needs attention? Prototype: Needs attention. Needs product confirmation.
- O2 · Retry affordance for Risk of Bias timeouts — no API today; the note copy points users to launch a new calculation. Candidate Wave 4 API.
- O3 · "Started by" column for families whose DTOs lack initiator — hide the column per family vs. "Not available"? Prototype shows "Not available" for consistency.
- O4 · RoB "hide completed default on" behaviour from the existing table — subsumed by D4's default filter; confirm no user relies on the old per-table toggle.
- O5 · Unknown-status fallback (D7) — align Bulk PDF's "Status unavailable" to "Unknown status (n)" or keep per-family? One rule should win in Wave 3.
Known leftover (engineers): ProjectAdminComponent.navigationItems (project-admin.component.ts) still lists { label: 'Systematic Searches', route: 'systematic-searches' }; that route now only redirects to searches (routed directly, outside ProjectAdminComponent, since C2a #3434), so the entry is dead and should be removed in a follow-up.
Deliverables index¶
StudyManagement.dc.html— interactive prototype (routes, widths, scenarios, live simulation)Artboards.dc.html— static frames: 3 routes × 1440/768/320 + Processing states (embeds the prototype; cannot drift)A11y Spec Board.dc.html— reflow / forced-colours / screen-reader / keyboard spectoken-mapping.md— DS token → Angular M3 mappingsource-truth.md— status vocabularies and DTO truth per family