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
JourneyPageis 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
- 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.
- 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.
- Put it on the registration door's rail, above the step, so it stays put while the step changes.
- 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
heroSplashinprojects/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.JourneyHeaderrenders it as the step header, whichJourneyPagewraps inmb-5 border-b border-line pb-5. PublicShellalready 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-heightandobject-fit: cover— the exact failure the setup screen's comment records. The prototype now borrows the application's tokens fromstyles.cssas 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:
PublicShellserves 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, andJourneySplashis the single element each page renders:JourneyHeaderfor 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.
JourneySplashrenders the artwork once, at 964:323, andJourneyHeader, 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.
SignUpJourneyreadsiam.v1.auth.registration-policyand 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-policyandPOST /api/signupare 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 lintclean, 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/signupon 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.