Skip to content

Study Management — Current-State Gap Analysis and Product Design

Outcome

SyRF should not replace Systematic Searches with a catch-all Study Management screen. The coherent end state is a Study management area with two contextual destinations and one cross-type processing destination. Navigation labels stay short while page titles remain precise:

Study management
├── Library (page title: Study library)
│   ├── study library and filters
│   ├── Add systematic search → Searches
│   └── Bulk Study Update → current/latest summary + Processing link
├── Searches (page title: Systematic searches)
│   ├── upload/create search and current/latest Search Import state
│   ├── per-search Bulk PDF commands and current/latest state
│   └── per-search Risk of Bias launch and current/latest state
└── Processing (page title: Processing activity)
    ├── canonical permitted history across the four durable families
    └── read-only detail + link to owning context

The programme name is Study Management. It is not necessarily another page label.

Evidence baseline

The repository and deployment comparison was made on 31 August 2026:

  • current main: d86c4362b7ec1ae43bdd203cc04fa4551db9c9de;
  • production web: 7.39.0, SHA 6c9b72e2f16ea45f91b0776c2cbf743dc7bc3a81;
  • PR #2990 preview: 7.88.0, SHA 8a5b6f7cdb7fae527e2b2e3aa68041ad93667c9e;
  • production exposes newProgressIndicators=true, robToolEnabled=false, and no bulkPdfUpload key;
  • the PR preview exposes newProgressIndicators=true, robToolEnabled=false, and bulkPdfUpload=false.

Production, current main, and a pull-request preview therefore represent different capability sets. An implementation may not claim deployed parity from source inspection alone. Staging proof must capture the exact version, SHA, user permissions, and feature flags.

Current routes and entry points

Concern Current route/surface Current entry point Gap
All Studies /projects/:projectId/studies Studies navigation Bulk Update is hidden in the table overflow menu; no revisitable history
Systematic Searches /projects/:projectId/admin/systematic-searches Studies navigation Owns several unrelated progress presentations and is already dense
New search/import Systematic Searches create dialog All Studies also links here Import progress is embedded in a search row and hard to rediscover
Bulk Study Update All Studies update dialog Table overflow menu Job progress is tied to the dialog; no correct owning history surface
Risk of Bias Systematic Searches Per-search action/history Status vocabulary and state treatment differ from other jobs
Bulk PDF Systematic Searches expandable search detail Per-search upload action Strong contextual commands/history exist, but only under flag and permission
Cross-type processing history None None Users must remember the owning screen

The project router mounts children below /projects/:projectId. The new canonical children are searches and processing, producing /projects/:projectId/searches and /projects/:projectId/processing. The current admin/systematic-searches URL remains as a compatibility redirect during rollout. The Library keeps /projects/:projectId/studies.

Current data and live-state architecture

ProjectDetailsDto projects four durable collections: Search Import, Bulk Study Update, Risk of Bias, and Bulk PDF Upload. The Angular client normalises these into NgRx entity maps and relates them to the current project. Initial project loading and project SignalR notifications refresh the same aggregate state.

Bulk PDF has the most mature client behaviour: session state, history, report, capacity state, cancel/retry, polling fallback, SignalR reconciliation, and monotonic revision handling. That behaviour remains contextual to Systematic Searches. Other job families should not copy its commands unless their server contracts provide equivalent safety.

Processing should derive rows from existing selectors through pure adapters. It must not keep a second operation database in component state. Reconnect and out-of-order rules must be made consistent before the aggregate list is trusted. Stale-event rejection belongs in each entity update path, before an older HTTP or SignalR payload can overwrite newer store state; presentation adapters remain pure. Bulk PDF already has a monotonic revision. The other three families require an equivalent authoritative revision/update sequence before they join the live aggregate.

Job capability and truth matrix

Family Exact current status source Progress source Time/owner truth Current commands MVP treatment
Search Import SearchImportJobStatus total, parsed, saved; parse errors/warnings LastModified; no created/completed/initiator Delete import job; upload creates work Current/latest state in Searches + read-only Processing history; unavailable fields are labelled unavailable
Bulk Study Update BulkStudyUpdateJobStatus total, parsed, validated, parse result created, intended last modified, initiating investigator Upload/start only Fix mapping/count/owner contract first; latest summary in Library, canonical history in Processing
Risk of Bias RiskOfBiasJobStatus search/PDF/text/processed/result/success counts created, last updated, completed; no initiator Launch calculation; no safe generic retry/cancel API Current/latest state in Searches + read-only Processing history
Bulk PDF BulkPdfUploadJobStatus upload parts and processing counts; report/failure data created, completed, creator, revision/heartbeat Initiate, complete, abandon, cancel, retry, report Keep commands in Searches; canonical monitoring/history in Processing

Rules applying to every family:

  • show the exact raw status or a safe Unknown fallback; never translate an unknown value to Running;
  • common groups exist only for filters, summary counts, icon/shape, and semantic tone;
  • derive a percentage only when numerator and a positive denominator describe the same unit;
  • do not treat zero, missing, indeterminate, and complete as synonyms;
  • never manufacture a date from DateTime.MinValue, Unix epoch, browser receipt time, or another job field;
  • display relative time only when an absolute ISO value is also available to assistive technology;
  • distinguish created, started, last updated, and completed rather than relabelling one value;
  • resolve a human initiator only from an authorised server projection or immutable job snapshot; otherwise show Not available, not a guessed Unknown Investigator; and
  • commands are explicit capabilities per job kind and permission, not properties of a common status group.

Correctness gaps that block UI implementation

Bulk Study Update

  1. BulkStudyUpdateJobDto.DateTimeLastModified does not convention-map from domain LastModified, allowing a year-one default and incorrect ordering.
  2. The DTO deconstruction assigns validated studies from parsed studies.
  3. PR #2990's “updated” tally uses parsed rows rather than the StudyUpdateParseResult matched or modified outcome, so a no-op file can appear fully updated.
  4. ProjectStandardDto exposes only public related investigators, so ordinary member initiators can disappear from the browser projection.
  5. There is no current cancellation or retry endpoint. The UI must not imply otherwise.

Search Import

The DTO has last modified but not created, started, completed, or initiator. Current progress is embedded in the project aggregate and can be delayed by the aggregate/change-stream path. PR

2612 proposes a staged import backend; it is a separate evolution that must preserve the

presentation contract without blocking an honest “not available” MVP.

Risk of Bias

The model has rich timestamps and counts but no initiator. Its enum includes terminal and error states, including Cancelled, but current project APIs do not provide generic cancel/retry commands. Status presence is not proof of a user capability.

Bulk PDF

The contextual workflow is mature, but deployed availability differs by environment. PRs #2976 and #2979 own its runtime/hosting contract. Study Management consumes their result and must not duplicate or redesign that backend in a UI slice.

Permission projection

Existing route visibility uses viewStudies for All Studies and viewSearches for Systematic Searches. Bulk Update launch uses BulkUpdateStudies. Bulk PDF projection and UI are filtered by its own permission and flag. The four job collections are not yet proven to share an equivalent server-side least-privilege projection. A client-only filter is insufficient because the payload would already disclose names, failures, users, or files.

Product behaviour

Library

  • Put Add systematic search and Bulk study update in a discoverable page action area when permitted; keep less frequent actions in overflow only if research supports it.
  • During an update, preserve progress after the dialog closes and show a compact current/latest summary with an obvious View in Processing link.
  • Keep the full canonical history out of the Library so it does not become a second processing dashboard.
  • The summary and linked detail use truthful result counts; they never claim every parsed row changed a study.

Searches

  • Keep upload/create, delete, and per-search management together.
  • Present Search Import state as part of the search context with clear progress, warnings, and errors.
  • Keep Bulk PDF upload/report/cancel/retry in the specific search context and show its current/latest state there.
  • Keep Risk of Bias launch and current/latest state associated with its search.
  • Link to Processing with the current search/type encoded as a filter and the referenced operation identified, so the link reveals that operation even when it has completed; do not duplicate full histories or embed project-wide Bulk Study Update history here.

Processing

  • The route is additive, read-only in the MVP, and canonical for the retained server history — the same truth the ProjectDetails aggregate holds. Server-side retention still applies (Bulk PDF prunes safely deletable jobs beyond 20 per search / 100 per project), so the page presents "history the server retains", never a claim that nothing was ever pruned; a durable archive or paged history API is the evidence-triggered follow-up already named below.
  • Default to Active + Needs attention, show the result count, and provide a clear Show all/reset.
  • Contextual View in Processing links from Library and Searches must reveal the operation they reference even when it has completed: the deep link identifies the referenced job (or otherwise overrides the default status filters) rather than inheriting a default that hides completed work. Routing tests cover a completed referenced operation.
  • Sort by the newest truthful activity timestamp while keeping focused/expanded rows stationary during live updates; defer resort until focus leaves or the user requests refresh.
  • Each item shows type, name/context, exact status, accessible progress, available timestamps and owner, bounded errors, job ID, and a native link to the owning workflow.
  • ProjectDetails currently loads the four permitted families as one aggregate. Its load/reconnect failure is therefore a whole-page data error; an empty or permission-filtered family must never be labelled as failed. Individual rows may still render truthful missing fields.
  • High volume initially uses client-side filtering/sorting followed by 25-row Show more increments over the current retained ProjectDetails aggregate. This bounds rendered DOM work, not payload size. A dedicated paged history API is a later backend slice if measured aggregate payload or retention proves inadequate.

State model and announcements

State Required presentation
Initial loading Page title and labelled busy region; no fake empty state
Empty Explain that no visible long-running work exists; link to permitted owning actions
Active determinate Exact stage, numeric unit/value, native progress semantics
Active indeterminate Exact stage plus descriptive “progress not yet measurable” text
Reconnecting Keep existing rows; announce delayed updates once, not on every retry
Partial row fields Keep the row; label only the unavailable timestamp, owner, total, or context
Aggregate load error Keep previously loaded rows when safe; explain load/reconnect failure and retry
Error/needs attention Text + icon/shape; bounded disclosure; copyable job ID
Complete Exact result and completion time when present; no 100% from missing totals
Unknown future status “Unknown status (value)” and safe neutral semantics

Live regions announce lifecycle changes and meaningful progress milestones, not every counter increment. Errors are keyboard-accessible and bounded by height/line count with an explicit expand action. Focus remains visible and is restored after dialogs/disclosures close.

Responsive and Material 3 contract

The programme consumes PR #2994's shared contract without owning its files:

  • use emitted --mat-sys-* roles for Material semantics and existing --syrf-* roles only for stable brand/domain meaning;
  • use the single adaptive navigation hierarchy: a persistent rail when space allows and the same destinations in a compact route-aware selector when it does not;
  • do not introduce Bootstrap utilities, private Material variables, hard-coded theme colours, a new font, or a competing breakpoint system;
  • use semantic tables at wide layouts and labelled cards or a contained, labelled scroller at narrow/high-zoom layouts; never cause page-level horizontal scrolling;
  • keep one page title, native route history/Back behaviour, keyboard parity, Escape behaviour, and identical current-route state across navigation modes; and
  • test 320 CSS pixels, 768 pixels, wide desktop, 200% zoom, 400% zoom/reflow, long names, large errors, forced-colours/high contrast, reduced motion, and screen-reader announcements.

Out of scope until separately approved

  • a universal backend Job aggregate or cross-project admin architecture;
  • Data Export in Processing;
  • notifications, saved views, bulk commands, analytics, or retention migration;
  • redesign of My Account, About, Admin, global navigation, or Material 3/theme ownership; and
  • merging, deploying, or modifying PR #2990 during this plan gate.