Task: Build the application shell, the route table and the bootstrap gate
Table of Contents
This page documents a task in the Implement the setup journeys in ores.web story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
The story has a rail that renders and no application to render it in. Build the two shells, the route table, and the bootstrap gate, so that a browser opened against an empty installation shows the Setup page and nothing else, and a browser opened against a working installation shows the sign-in page.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Implement the setup journeys in ores.web |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-09-27 |
3. Acceptance
AppShellandPublicShellexist, built on the restored primitives: a visitor gets a shell with the brand and one screen, a signed-in person gets a shell that names the session it is in and the way out of it, and each renders one screen.- The route table names the setup page, the sign-in page and the application root, and a route that needs a session redirects an unauthenticated visitor to sign-in.
- The bootstrap gate holds from the server's answer: while the system reports bootstrap mode, the setup page is the only route that renders and a sign-in attempt is refused.
- The bootstrap status reaches the browser through a hand-written BFF route and a parse of the generated schema, so the gate reads the server rather than a constant.
- The workspace typechecks, passes the format check, tests green, and builds.
4. Plan
Three units, in one commit.
4.1. Unit 1 — one read, shared by both sides
GET /api/bootstrap on the BFF, calling the bootstrapStatus() the wire client
already has, and bootstrapStatusSchema as the one definition of the reply,
exported from the package root for the BFF and from the browser entry for the
interface. This is the one item of plan step 4 that lands here, because a gate
that cannot read the flag is not a gate; the rest of step 4 — create
administrator, seed profile read, provision, progress, retry — follows.
The route is unauthenticated, because it is asked before a session can exist. It opens one connection and closes it on both paths, since it carries no session to keep alive.
4.2. Unit 2 — the gate as a rule, not a component
BootstrapProvider holds loading, ready and unreachable. A server that does not
answer is a state with a retry rather than a fall-through to a sign-in form,
because answering "not in bootstrap mode" for a server that did not speak would
offer a door to a deployment that may have no accounts.
AppRoutes takes the gate and the session as arguments and returns the route
table. That is what makes the story's rule testable: in bootstrap mode every path
renders the setup page, and no password field is rendered anywhere. It renders
the setup page rather than redirecting to it, because a redirect leaves a URL
somebody can share that leads nowhere.
The server already refused a sign-in in bootstrap mode — the login route asks the same question before it uses the credentials — so the gate is the interface half of a rule that holds whether or not the interface obeys it.
4.3. Unit 3 — the shells and the screens they carry
PublicShell and AppShell on the restored primitives, with the session passed
to AppShell as props so it renders in a test without a server. SetupPage
states the mode and the server's own sentence. HomePage shows what the session
holds, because a root that shows real state is readable and a hero that promises
features is not. SignInPage is the path that exists: identity, password, and the
party choice when an account works in more than one. brand.ts gains the header
mark, the first consumer of the artwork the previous step restored.
5. Measurements
Every number this task claims about a tree, with the command that produced it
and the commit it was measured at. A number without both is not a measurement,
because a clean-up record describes a tree and trunk moves under a branch: a
consumer count, a gate's component count and a suite's size all change with no
code changing, and the reader of a bare number cannot tell which tree it came
from. Where the raw output matters, put it in an evidence/ file beside the
task and name that file in the Command column.
| What | Value | Command | Commit |
|---|---|---|---|
| The workspace suite | 109 cases in 15 files | npm test |
f3cc9fcad2 |
| … of which new | 9: 6 for the gate, 3 for the shells | npm test |
f3cc9fcad2 |
| The codegen suite | 929 cases | python3 -m pytest projects/ores.codegen/tests -q |
f3cc9fcad2 |
| The browser bundle | 413.72 kB, from 402.07 kB | npm run build --workspace @ores/web |
f3cc9fcad2 |
The bundle grew because the shells and the screens are now reachable from the entry point. Before this change nothing imported them, so nothing was bundled.
6. Notes
- The gate needed one read, so one read came forward. Plan step 4 owns the
bootstrap and journey subjects in the browser. The gate cannot hold without
knowing the flag, so
GET /api/bootstrapand its schema land here and the rest of step 4 follows. The alternative was a gate over a constant, which would have been a gate that agrees with the server only by accident. - The shells take the session as props. Reading it from the context inside
AppShellwould have made the shell untestable without a server and a session, which is the shape of thing that ends up tested by hand. - The sign-in screen is not the Entry screen. Self registration, the recovery paths and the wording of each refusal belong to the Entry story, which the story's out-of-scope names. What is here is the path that exists.
- The catalogue keeps the keys of the deleted screens.
home.quickActions,nav.site,component.*,shortcut.*and the history and dialog blocks are reached by nothing, and the catalogue test only compares key sets across languages. Filed as a capture rather than cleaned up here: removing them touches three catalogues and needs an allowance for the keys built by interpolation. - No route-level test of the BFF route. The component has no route tests at all — the login route is not exercised at that level either — and one would need a broker stub. The client method it calls is tested, and the shape is parsed on both sides at run time.
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 |
|---|---|
| #2295 | [web] Build the shells, the route table and the bootstrap gate |
9. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
10. Result
The two shells exist, the route table is wired, and the gate reads the server's own answer.
A browser opened against an installation in bootstrap mode shows the setup page at every path and is offered no sign-in. A browser opened against a working installation shows the sign-in page to a visitor, and the application shell — with the tenant and the party the session is in — to somebody signed in. A server that does not answer is a third state: it says so and offers to ask again.
The gate is a function of two states rather than a component that reads them, so the rule is six tests rather than something a person has to try in a browser. The shells take their session as props for the same reason.
One read came forward from plan step 4, because the gate cannot hold without it. The rest of that step — create administrator, seed profile read, provision, progress and retry — is what follows.