Skip to content

feat(web): honour GATEWAY_ENABLED across every remote-assignment surface - #1506

Merged
joshunrau merged 3 commits into
mainfrom
feat/gateway-flag-in-web-config
Aug 12, 2026
Merged

feat(web): honour GATEWAY_ENABLED across every remote-assignment surface#1506
joshunrau merged 3 commits into
mainfrom
feat/gateway-flag-in-web-config

Conversation

@thomasbeaudry

@thomasbeaudry thomasbeaudry commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Replaces #1502, which was closed with:

This is completely wrong. Right now, this is stored in the env. The entire backend architecture is
based on this env variable with conditional module loading. Just use the env variable in the
frontend. Pass this into the config on frontend and use that.

This is that rewrite. There is no new setting, no schema change and no migration — nothing in
packages/schemas, apps/api or the settings page is touched.

What

GATEWAY_ENABLED already reaches the frontend as config.setup.isGatewayEnabled
(apps/web/src/config.ts), and apps/api/src/main.ts already mounts AssignmentsModule and
GatewayModule behind when: 'GATEWAY_ENABLED'. But only one of the six surfaces that offer remote
assignments read it — the datahub tab. The sidebar read the API's copy of the flag out of
GET /v1/setup, and the rest checked nothing at all.

So on an instance deployed without the gateway you still got a Remote Assignment item in the
sidebar, a tutorial step pointing at an assignments tab that is not rendered, two routes that render
a page whose endpoints were never mounted, a Remote Assignment Templates page whose only consumer
is POST /v1/assignments/:id/email, and a setting for the default validity of an assignment that can
never be created.

All six now read the config:

Surface Before
Sidebar Remote Assignment item setupStateQuery.data.isGatewayEnabled
Datahub Assignments tab already config.setup.isGatewayEnabled
/session/remote-assignment unguarded → redirects to /dashboard
/datahub/$subjectId/assignments unguarded → redirects to the table tab
Walkthrough Assignments step unguarded → omitted from the step list
/group/email-templates + its nav item unguarded → redirects to /dashboard
Default Assignment Validity setting unguarded → section not rendered

isGatewayEnabled stays on $SetupState: about.tsx displays it and gates the gateway healthcheck
loader on it. That is a diagnostics readout of what the API booted with, which is a genuinely
different question from what the frontend should render, so it keeps using the API value.

Why the validity setting is hidden rather than left alone

It is the one place this rule reaches a setting rather than a route, so it is worth stating. The
value it writes, defaultAssignmentDurationDays, is read in exactly one place — seeding the expiry
field when creating a remote assignment. With the gateway off it configures nothing. An inert input
is worse than an absent one: an admin who changes it gets a save confirmation and no way to learn it
had no effect. The section and its separator are hidden together, so the card does not grow a stray
divider.

.env.template

The comment on GATEWAY_ENABLED said only "Whether or not the gateway should be activated", which
does not tell an operator that remote assignments depend on it — the word "gateway" reads as a
deployment topology detail, not a feature switch. It now says it must be true to send remote
assignments.

Tests

  • apps/web/src/hooks/__tests__/useNavItems.test.ts — the remote-assignment item appears when the
    gateway is deployed and the user can create an assignment, and is absent when either does not
    hold; the email-templates item additionally requires mail to be configured.
  • apps/web/src/__tests__/gateway-route-guards.test.ts — new, table-driven over all three
    beforeLoad guards: each throws a redirect to the right destination with the gateway off, and
    returns cleanly with it on. This is the part that protects a bookmarked link, which the sidebar
    test does not reach.
  • testing/src/specs/remote-assignment.spec.ts — a direct-URL navigation to the assignments tab,
    which exercises beforeLoad on a cold document load rather than a client-side tab click.

The gateway-disabled branch of the guards has no e2e coverage by construction: the suite runs a
single environment with GATEWAY_ENABLED=true, and the API decides at boot whether the assignment
endpoints exist, so the flag cannot be flipped mid-suite. It is covered in the unit tier instead.

I did get an incidental full-stack look at the disabled path — a local run with
GATEWAY_ENABLED=false failed 25 tests, every one of them a test asserting that a remote-assignment
surface exists, and nothing else. That is the suite reading correct gating as an outage, and it is
about as close to a gateway-off integration check as this repo's shape allows.

pnpm lint (33/33) and pnpm test (726 passed, 1 skipped) are green. pnpm test:e2e is 143/145 on
two consecutive runs, with a different pair failing each time — authorization.spec.ts:147 twice
on the first run, then static-pages.spec.ts:52 and subject-detail.spec.ts:51 on the second.
authorization.spec.ts passes 29/29 run on its own. None of the four touches a gateway surface;
they are parallel-load flakes on this machine.

🤖 Generated with Claude Code

Assignments are served through the gateway, and the API only loads
AssignmentsModule and GatewayModule when GATEWAY_ENABLED. The frontend
only partly reflected that: the sidebar item read the flag back from
GET /v1/setup, the subject tab read it from the env config, and the two
routes plus the tutorial step were gated on nothing at all.

All four now read config.setup.isGatewayEnabled, which is the same env
variable the backend switches on. The routes redirect in beforeLoad
rather than rendering a page whose endpoints are not mounted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thomasbeaudry

Copy link
Copy Markdown
Collaborator Author

Okay so I see the gateway ,env variable. The problem with using a variable is that the person doing the setup needs to know that they should configure it (and understand what it the flag does) instead of just naturally finding it while poking at the software. If you want to keep it that way, you need to modify the comment in the .env since currently it says:

Whether or not the gateway should be activated

This makes no mention of how it's needed for the remote assignemnts to work. When I first setup ODC I thought gateway = a second server that is outside of the hospital nework, not that gateway was needed for remote assignment functionality. You should change the comment to something like:

Controls gateway activation; must be set to true to send remote assignments

Personally I'm find with both a .env variable (for those that like them), and having a toggle that overwrites that variable that stores a DB value - your call

@joshunrau joshunrau left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Premise and mechanism both look right — GATEWAY_ENABLED is injected into the bundle at container
start, so config.setup.isGatewayEnabled really is the same value the API switches its module list
on, and the guards sit where apps/web/AGENTS.md says redirects go. Lint is green on the merge
result and the nav-item test does fail when the condition is removed, so it is a real test. Three
things before this merges:

  1. /group/email-templates is a fifth remote-assignment surface and is still ungated. Its entire
    body is the assignment-email template manager — the heading is literally "Remote Assignment
    Templates" and the subtitle is "Used when emailing a remote assignment link"
    (apps/web/src/components/GroupEmailTemplates/GroupEmailTemplates.tsx:188) — and the endpoint
    that sends the mail lives in the gateway-gated AssignmentsModule. Add
    config.setup.isGatewayEnabled to the nav condition at apps/web/src/hooks/useNavItems.ts:90 and
    a beforeLoad guard in apps/web/src/routes/_app/group/email-templates.tsx, the same shape as
    the two you added.

  2. The two new guards have no unit test. useNavItems.test.ts covers the sidebar only, so the
    redirect itself — the part that protects a bookmarked link — is untested in both tiers, which is
    not quite what the PR description says. They are easy to reach without a router: mock @/config
    with isGatewayEnabled: false, import the route module, and call Route.options.beforeLoad — it
    throws the redirect. I checked that this runs under vitest --project web. The test belongs in
    apps/web/src/__tests__/, not under src/routes/.

  3. The Default Assignment Validity (Days) field on /admin/settings
    (apps/web/src/routes/_app/admin/settings.tsx:191) has the same problem in miniature: with the
    gateway off it configures something that can never apply. Either gate it too, or add a line to
    the PR saying why it is intentionally left alone — the about.tsx "diagnostics readout"
    rationale does not obviously cover a settings input.

If you hand this to Claude Code, Opus 5 is the right size — three files across apps/web plus
new tests, and one of the items is a judgment call rather than a mechanical edit.

Reviewed at commit 3400c0b.

thomasbeaudry and others added 2 commits August 11, 2026 23:50
…NABLED

Addresses review on #1506.

/group/email-templates is a fifth remote-assignment surface: its page is
the assignment-email template manager, and the endpoint that sends that
mail lives in the gateway-gated AssignmentsModule. Its nav item and route
now read config.setup.isGatewayEnabled like the other four.

The Default Assignment Validity setting configures the expiry of a remote
assignment, so with the gateway off it sets something that can never
apply. Its section is hidden rather than left inert.

The three beforeLoad guards now have unit tests, so the redirect that
protects a bookmarked link is covered rather than only the sidebar.

The GATEWAY_ENABLED comment in .env.template said only that it activates
the gateway, which does not tell an operator that remote assignments
depend on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@joshunrau
joshunrau merged commit 090af4a into main Aug 12, 2026
5 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.

2 participants