Skip to content

feat(web): warn before leaving an instrument whose responses are unsaved - #1609

Merged
joshunrau merged 1 commit into
DouglasNeuroInformatics:mainfrom
joshunrau:feat/block-leaving-active-instrument
Oct 4, 2026
Merged

joshunrau merged 1 commit into
DouglasNeuroInformatics:mainfrom
joshunrau:feat/block-leaving-active-instrument

Conversation

@joshunrau

Copy link
Copy Markdown
Collaborator

Closes #717.

Instrument state is not saved until it is submitted. A stray sidebar click, a refresh or a closed tab discarded the clinician's answers without asking. Now, from "Begin" until the summary, leaving the page asks for confirmation first (Yes / No dialog in the app, the browser's native prompt on refresh or close). This covers both scalar and series instruments. Before, the warning appeared only while a file upload was pending.

What changed

  • One blocker, owned by InstrumentRendererContainer. Both renderers pass it the injected NavigationBlocker, and it is active while the content step shows (index === 1). SeriesInstrumentRenderer takes the prop now. The upload-only blocker in FileInstrumentContent is gone. TanStack runs every registered blocker in turn, so keeping it would have shown a second dialog during uploads. Uploads are still covered, because the renderer stays on the content step until onSubmit resolves.
  • NavigationBlocker (web) holds beforeunload only while active. It passed enableBeforeUnload: true, so refresh prompted for as long as the blocker was mounted. That was harmless while it lived only inside the file upload screen, but it would have prompted on the overview and summary once mounted for the whole instrument.
  • Ending a session bypasses the blocker (ignoreBlocker in Sidebar, Navbar, and the render page's no-session redirect). Sidebar's End Session already confirms, and endSession() runs before the navigation. Blocking would leave the clinician on a form with no session, where submit fails on currentSession!. The e2e test reproduced exactly that before the fix.
  • packages/react-core/AGENTS.md updated: it said only the scalar path takes NavigationBlocker.

Verification

  • Unit (react-core): ScalarInstrumentRenderer.test.tsx checks the blocker is inactive on the overview, active once begun, still active while files upload, and inactive at the summary. SeriesInstrumentRenderer.test.tsx checks it stays active between items and is released at completion.
  • Unit (web): src/__tests__/navigation-blocker.test.tsx runs against a real router. It covers beforeunload while active and inactive, "No" keeping the location, and "Yes" completing the navigation.
  • E2E: testing/src/specs/instrument-navigation-block.spec.ts. Declining keeps the clinician on the instrument with answers intact. Confirming leaves. No warning after submission. End Session skips the warning and ends the session.
  • I made each new test fail on purpose: block on summary, never block, enableBeforeUnload: true, and dropping ignoreBlocker.
  • pnpm lint, pnpm test (1634 passed) and pnpm test:e2e (276 passed) are green locally.

Not covered

  • Gateway (remote assignments) has no router and passes no NavigationBlocker, so patients get no warning on refresh or close. That's out of scope here (Area: Web) and would need its own beforeunload-only blocker.
  • The mobile Navbar's End Session still has no confirmation of its own, so it now ends a started instrument without any prompt (same as before this PR).
  • The leave dialog appears only for navigations inside the app or page unloads. Interactive instruments run in an iframe, and links inside the iframe are not intercepted.

Co-Authored-By: Claude Opus 5.5

Instrument state is not saved until it is submitted, so a stray click on
the sidebar, a refresh or a closed tab silently discarded a clinician's
answers. The navigation blocker now stays active from "Begin" until the
summary, for scalar and series instruments alike, instead of only while a
file upload is pending.

- InstrumentRendererContainer owns the single blocker. TanStack runs every
  registered blocker in turn, so the upload-only one in
  FileInstrumentContent is removed rather than kept alongside it.
- The web NavigationBlocker now holds beforeunload only while active.
  Before, it prompted on refresh for as long as it was mounted.
- Ending a session bypasses the blocker. The sidebar already asks for
  confirmation, and once the session is gone the instrument cannot be
  submitted, so blocking would only strand the clinician on it.

Closes DouglasNeuroInformatics#717

Co-Authored-By: Claude Opus 5.5
@joshunrau
joshunrau merged commit e5ac748 into DouglasNeuroInformatics:main Oct 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add modal block to leaving active instrument

1 participant