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 A Bulk PDF row detail links back to its owning search's upload details with Open upload (
/searches?openUpload={searchId}, #3749); it is a link to the owning page, not a command. - 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. When nothing is Active or Needs attention, the untouched default shows the latest completed outcomes instead (SM-11); explicit filters are respected. 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. Amended by R7 (3 November 2026): a bulk PDF failure leaves Needs attention for Completed when a later upload for the same search completes, or when a retry window it was granted has lapsed. It still reads as failed. A failure that was never granted a retry stays in Needs attention.
- 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), in local time with an explicit UTC offset. Creation/completion timestamps are not substituted for missing update times (owner correction #3749, 2026-09-26).
- "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, consistently labelled local timestamps, initiator, job ID with Copy. The previous UTC-only detail convention is superseded by owner correction #3749 (2026-09-26). 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 · closed (3 November 2026, #4705) · "Started by" column for families whose DTOs lack initiator — hide the column per family vs. "Not available"? Decided: search import (no initiator in its DTO) omits the "Started by" line and cell; families that record one keep "Not available" when a row lacks it.
- 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.
Design review decisions and open items (R1–R15, 3 November 2026)¶
R1–R13 were decided by Chris after the November 2026 design review, and R14 and R15 (asked as D2 and D3 on the progress page) on 3 November 2026. They settle the design; the slices that carry them are in the implementation plan's "Design-review slices" section.
- R1 · Per-study PDF status is tracked by the server. Retrieving a study for review returns at once. If its PDF status is unconfirmed, the server checks the authoritative storage in the background, saves the result on the study and pushes it to subscribed clients over SignalR. The browser never sets the shared status and sends no check request. A confirmed result is reused. "Not yet confirmed" or "Checking" is distinct from available and unavailable, and a stored path alone never counts as a PDF. Slice 8
- R2 · Two PDF capabilities. Upload submits a candidate PDF and never changes the study's PDF. Set
accepts a proposal, attaches, replaces or clears the association, including a bulk-delivered PDF. Bulk upload
with direct attach needs both. Clearing doesn't delete storage. Today's default grants are unchanged; reviewers
keep proposal access through
SubmitPdfCorrections. Workspace slice W2 - R3 · Studies structure: C+B′. Decided on 3 November 2026. "Studies is one page with a light Searches pane for
choosing a search and seeing its status. Importing, adding PDFs, history and deleting live on a Searches page
reached from the pane, with a back arrow. One Activity sheet covers both." Drawn as option C+B′ on the owner's
prototype board,
Study-Management-Trial.dc.html, beside the options it was chosen over (A three pages, B and B′ two pages, C one workspace, D searches first). Workspace slices W1–W6, behindstudiesWorkspace - R4 · Search overview for non-admins. Read-only search names and study counts, off by default, granted to a group through the existing group model. The server enforces it and returns only that subset; non-members of private projects are refused. Slice 7
- R5 · Duplicates: warn only. Review says SyRF doesn't remove duplicates, and the import warns on a matching search name or file name and size. No rejection, merging or server-side detection; that belongs to the deduplication programme.
- R6 · One Status column on Searches. The import state, then a short PDF line; detail in the expander. Slice 4
- R7 · Needs attention. A failure leaves it when a later attempt for the same search completes or no action remains. History is kept, still shown as failed. Slice 5
- R8 · "Import a search" in both
searchImportWizardstates now. No further legacy-dialog work; it's retired when the wizard flag settles. Slice 11 - R9 · Overview's study count is the current Library total, linked to the Library. Imported-record counts stay per search on Searches. Slice 11
- R10 · Staged rollout behind flags. See the implementation plan's rollout rule.
- R11 · Pure corrections need no flag; each PR states its flag decision.
- R12 · The search-read security fix ships unflagged, after its HTTP repro test and a security review.
- R13 · What reviewers see about studies: blinded by default. Decided on 3 November 2026. Reviewers see only their own work on a study by default. A project may relax this, with a concise warning about what it changes. The setting lives at its logical level, a stage setting, and is signposted from where it matters: the board reaches it from the Library as "What reviewers see". Its screens in Stage setup aren't designed yet, so no slice builds it.
- R14 · What a reviewer sees in the study detail before "What reviewers see" is designed: A. Decided by Chris on 3 November 2026 (asked as D2). A reviewer sees in the detail exactly what the Library shows them today: their own work, the citation and the PDF. Others' decisions stay hidden. Workspace slices W1 and W2
- D4 · Set on a bulk-delivered PDF: A. Decided by Chris on 4 November 2026. R2's Set may replace or clear a PDF
that a bulk PDF upload delivered: the same write clears the delivery marker (
BulkPdfDeliveredAt,BulkPdfDeliveredPath), so "first delivery wins" is relaxed only through this explicit action. Any set or clear is refused while a bulk PDF upload for the study's search is unfinished. A later bulk upload may deliver to a study whose path was set by hand, as to any undelivered study. Clearing never deletes the stored file. Workspace slice W2 - D5 · Choosing a PDF file from the computer: A. Decided by Chris on 4 November 2026. W2's Add, Replace and Propose take a link; choosing a file becomes its own later slice, a single-file pipeline that scans and then copies, reusing the bulk stages.
- R15 · "Who can see search names" offers All members (B). Decided by Chris on 3 November 2026 (asked as D3). The dialog offers named groups and All members. Choosing All members shows a warning: "Everyone in the project, including reviewers, will see this project's search names and study counts." Nothing is granted by default. Workspace slice W4
- D6 · Slice 9 now, over the proposals the study detail stores: A. Decided by Chris on 4 November 2026. Slice 9
("PDFs to check") is built now, over the proposals W2 stores (
pmStudyPdfCorrection), behindstudiesWorkspace. Each proposal carries its source, so checked uploaded proposals from L9 (#3947) join the same list later. L8 (#3945) and L9 keep their own order and owner. Workspace slice 9 - D7 · One pipeline for single uploaded PDFs: A. Decided by Chris on 4 November 2026. Single uploaded PDFs (#4751 and L9's proposals) go through one pipeline, #4683's isolated checking service. #4751 waits for #4683's questions. Slice 9 adds no upload and no scanning.
- Wave 7 open questions: settled by the specs' recommendations. Decided on 4 November 2026. The open questions in the specs for S12, #4719, #4720, Bulk study update in Studies and slice 9 were settled by adopting each spec's recommendation. Each is recorded, one line per question, under "Settled by recommendation" in Studies wave 7: the remaining slices on the chosen board. Workspace wave 7
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