Task: Use one rail and one splash header on every setup screen

Table of Contents

This page documents a task in the Implement the entry journeys in ores.web story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

Every screen that sets something up draws the same chrome. The rail comes from JourneyPage and the splash header from JourneyHeader, and the setup journeys, the registration door and the sign-in dialog all use them unchanged. The setup screen already leads with the project's artwork; the sign-in dialog does not, and the registration door the Entry story adds must. A screen that draws its own rail, or its own banner, is the defect this task exists to prevent.

2. Status

Field Value
State DONE
Parent story Implement the entry journeys in ores.web
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-09-30

3. Acceptance

  • The registration door renders on JourneyPage's rail, unchanged: the same entry classes, the same three states, the same circled mark with a tick behind the person, and the same footer.
  • The splash is the header JourneyPage is given, and it stands on every step rather than only the first.
  • The splash keeps its own 964:323 ratio: the element states its width and height, and nothing crops it or states a height that is not the ratio's, because that is what cut the top and the foot off the wordmark before.
  • The sign-in dialog carries the same banner element above its form.
  • The banner does not crowd the party picker, the forced password change panel, or the refusal notices: it stands above the work, and the work still reads.
  • A test asserts both the rail and the banner render on the screens that carry them, so a future layout change cannot drop either silently.

4. Plan

  1. Decide where the banner belongs. The setup screen draws it per screen; a shell that draws it for every visitor is the smaller change and the one that makes "every dialog" true by construction, so try the shell first and fall back to the page if the shell cannot tell a door from a form.
  2. Put it on the sign-in dialog, above the credentials form and above the party picker, because the picker replaces the form rather than following it.
  3. Put it on the registration door's rail, above the step, so it stays put while the step changes.
  4. Assert it in the shell's or the page's test, whichever ends up owning it.

5. Measurements

What Value Command Commit
Screens that need the banner 2, sign in and sign up grep -rn "heroSplash" projects/ores.web/packages/web/src 69d002553c
Screens that already carry it 1, the setup screen grep -rn "heroSplash" projects/ores.web/packages/web/src/journeys/FirstRunJourney.tsx 69d002553c
Places that render the splash element after this 4: JourneyHeader, the sign-in dialog, the door's closed/failed states, and the door's rail header grep -rn "JourneySplash" projects/ores.web/packages/web/src 12b08cdc30
Places that render the artwork directly after this 1, inside JourneySplash itself grep -rn "heroSplash" projects/ores.web/packages/web/src 12b08cdc30

6. Notes

  • The artwork is heroSplash in projects/ores.web/packages/web/src/assets/brand.ts, which the site publishes at /OreStudio/projects/ores.web/packages/web/src/assets/ore-studio-splash.png; the prototype uses that URL so what a reviewer sees is the real banner and not a drawing of one.
  • The rule the setup screen follows, and this task carries: the image states width 964 and height 323, the class is h-auto w-full rounded-md border border-line, and no height is declared anywhere. JourneyHeader renders it as the step header, which JourneyPage wraps in mb-5 border-b border-line pb-5.
  • PublicShell already draws the header mark and the application name for a visitor. The splash is the larger artwork above the work, so it is a second element rather than a replacement for the header.
  • The prototype needed both corrections and both are now in it. It drew a rail of its own, and it cropped the splash with max-height and object-fit: cover — the exact failure the setup screen's comment records. The prototype now borrows the application's tokens from styles.css as well, because a prototype in a palette of its own answers the wrong question.
  • Where the banner lives, decided. The shell was tried first, as the plan said, and rejected: PublicShell serves both the setup journeys and the door, and the journeys draw the splash inside their own panel above the step. A shell-level copy would double it on every setup screen, so the shell cannot tell a door from a journey. The page owns it instead, and JourneySplash is the single element each page renders: JourneyHeader for the setup journeys, the sign-in dialog above its work, and the door for both its rail header and its closed, failed and loading states.

7. Test Scenarios

Manual QA scenarios (scaffolded via compass add test_scenario) that verify this task. Link new ones here as they're created; the scenario doc itself links back via its "Verifies task" field.

Scenario State Notes
     

8. PRs

PR Title
#2441 [web,iam] Build the registration door behind one banner

9. Review

# Comment summary File Decision Notes
1 The acceptance names a forced password change panel the sign-in dialog does not have. pages/SignInPage.tsx Accept Recorded rather than built: the panel belongs to the sign-in scope this story has not reached. The banner stands above the card's body, so any panel the dialog gains sits below it and the constraint holds by construction.
2 The door tells a stranger whether a username or an address is already taken, which is account enumeration. bff/src/server.ts Accept Inherent to a registration form: it cannot be usable and silent at once. The door is behind the deployment's own switch, the route shares the sign-in limiter, and the alternative — accepting a name already in use — is worse. Recorded rather than mitigated further.
3 A closed door renders a narrow card in the wide public shell. journeys/SignUpJourney.tsx Decline The route cannot know the policy before the door reads it, and the open door needs the width for its rail. The card is what the prototype draws for the closed state; the extra width is the shell's, not the screen's.
4 The confirmation step exists before any registration has happened. journeys/signUpSteps.tsx Decline The rail is derived from one step list, which is what keeps the rail and the steps from disagreeing. The step is unreachable until the write resolves, because the review step is final and the page advances only on success.
5 misspell-fixer . exits 1 over the repository. (repository root) Decline The hits are the upstream identifier in sprint 25 task records and files under node_modules/, all present on origin/main in files this branch does not touch. An isolated run over this change's paths reports "nothing to replace".
6 GET /api/registration-policy is unauthenticated with no limiter, and opens a broker connection per call. bff/src/server.ts Accept Fixed: a policy limiter on its own budget, separate from the sign-in attempts, and the 429 the error contract was missing.
7 Every refusal answers 409, which does not fit a weak password or a deployment that does not accept registrations. bff/src/errors.ts Accept Fixed: the status follows the code — 409 for a name or an address already in use, 400 for a weak password, 403 for a refusal of the request itself.
8 An unknown server code falls back to invalid-request, so a new code reads as a client defect. bff/src/errors.ts Accept Fixed: the fallback is signup-refused, a refusal of its own.
9 The limiter runs after the body validation. bff/src/server.ts Decline Deliberate, and the order the sign-in route already uses: a malformed body is rejected before it costs the caller budget.
10 Registration tells a stranger whether a name is taken, so the limit must be tight enough. bff/src/server.ts Accept Already recorded at #2 as inherent. Both routes are limited now, which is the mitigation a registration form admits.
11 The create step's empty-message fallback reuses the closed door's sentence. journeys/SignUpJourney.tsx Accept Fixed: signUp.createFailed, added to the three catalogues.
12 read() sets state after the screen has gone, or after server changed. journeys/SignUpJourney.tsx Accept Fixed: the effect hands the read a flag and sets it on unmount, and the read writes nothing once it is set.
13 success with usableNow false could open a door that then fails at submit. journeys/SignUpJourney.tsx Decline Handled, and now stated where the decision is made: the door opens either way, and the review step and the confirmation both say the account will wait. A tenant that nominates no role is refused by the read itself.
14 signUpJourney is a new required prop of AppRoutes. AppRoutes.tsx Decline Supplied by ConnectedApp and by the route table's own test; the typecheck is what holds it.

10. Result

The banner is one element and the registration door exists.

  • One element. JourneySplash renders the artwork once, at 964:323, and JourneyHeader, the sign-in dialog and the door all render it. The shell was tried first and rejected: it serves the setup journeys as well as the door, and the journeys draw the splash inside their own panel, so a shell-level copy would double it. A test asserts the rendered element states both dimensions and carries no cropping class.
  • The sign-in dialog. It carries the banner above its work, and the work — the credentials form or the party picker — stays below it, because the picker replaces the form rather than following it.
  • The door. SignUpJourney reads iam.v1.auth.registration-policy and the password policy before it offers anything, and the answer decides which screen a person gets: the closed notice, which names the closure, or the same rail the setup journeys use. The branch is one function, doorState, so it is stated once. The write is deferred to a review step that reads back the tenant, the party and the role, and the confirmation states which of the two accounts was produced.
  • The routes. GET /api/registration-policy and POST /api/signup are new, both unauthenticated, both forwarding the bare hostname — the port is not part of a tenant's address — and the second shares the sign-in limiter. A refusal keeps the server's own code, mapped onto the browser's error contract, so the screen branches on a value rather than a sentence.
  • Verification. TypeScript: typecheck clean; 291 tests pass across the three packages (web 167, BFF 73, wire protocol 51), of which the new ones are 9 BFF, 8 door, 8 steps, and 3 route-table assertions. Formatting clean. Docs: compass lint clean, all seven doc-lint steps pass, and the site publishes the 28 changed pages with no broken link.
  • Live, against the running deployment. POST /api/v1/iam/auth/signup on the environment's HTTP gateway answers {"success":false,"error_code":"signup_disabled"}, which is the defect the survey found, closed: the live path reads the deployment's switch and refuses the registration it should never have accepted. The positive path — an account actually created, in either state — was not driven live, because it would change the environment's settings and leave an account behind.
  • The documents are corrected with it. Sign up's coverage table now names the policy read and the refusal code, and four of its seven gaps have closed. What remains is the release of a waiting account, the approval workflow, and a notice that somebody is waiting.

Emacs 29.3 (Org mode 9.6.15)