feat(home): phase 3 — the homepage #6

Merged
whitlocktech merged 1 commits from feat/phase-3-homepage into main 2026-08-20 05:14:02 +00:00
Member

Phase 3 of PLAN.md §13. Replaces phase 1's scaffold with the real homepage: hero, the data path as inline SVG, the self-hosted argument, all five capability groups, the get-started CTA, and the reserved demo slot.

Branched from main after phase 2 (#5) merged.

The three decisions, taken before any of it was written

Recorded in PLAN.md §10 as D17–D19.

D17 — the data path is drawn generically, and captioned specifically. The nodes read "your game server", "sidecar", "Runic Gateway", "browser and app", because §10 says a reader should not need to know this org's repository layout to understand the picture. It does not hide what ships: the sub-labels and the caption name ServUO and uo-link outright, since there is exactly one implementation of the shape today.

D18 — all five groups on the homepage, named only. Not three with a link out: Integration and Infrastructure carry the module and self-hosted arguments, and hiding them until phase 4 would make the front page look smaller than the product. The per-capability argument stays /features/'s job, so there is one copy of it.

D19 — the hero leads with the emblem rather than type alone. It costs what D16 already accepted — raster art a mounted theme.css cannot recolour — but every size is derived from whichever logo.png is in force (D14), so the hero, header, tab icon and installed icon still change together from one file.

The capability list is now checked end to end

src/data/capabilities.mjs holds the five groups as data, and every Game-intelligence item names the module-uo capability slug it comes from. The build fails if the page and platform.json disagree in either direction — an unlisted slug means the site is under-selling, an unknown one means it is advertising something that is gone (§1).

Closing that loop needed a fifteenth fact in checkFacts.mjs. §12 names the capability list as an externally-sourced fact and nothing re-read it, so the whole chain rested on someone remembering. It is now Module-uo main:module.jsoncapabilities, sorted before comparing, so a reordered manifest is not a failure but an added or removed slug is.

It immediately found something: the site was omitting two of the module's eight capabilities. guilds and governors were absent from §10's grouping. Both are real and documented (shard_guilds, shard_governors, shard_governor_terms), so they are now on the page and in PLAN.md.

Two defects found by standing the build up, not by reading it

The demo slot's markup contract had no guard. applyBrand.mjs reveals the demo link by replacing an exact pair of empty attributes in built HTML. An attribute inserted between them, or href written second, produces a build where the mount sets a demo URL, the boot log says nothing, and the link is simply never there. checkBrand.mjs now checks both halves — the slot exists, and nothing writes data-demo-url outside the exact pair — and derives the literal from the same expression applyBrand.mjs uses, so the two cannot drift.

The header nav overflowed on every phone. Rendering the homepage in a 390px frame measured scrollWidth 433 against a 390px viewport: four links plus the lockup do not fit, so the whole page scrolled horizontally. This is pre-existing from phase 1, whose own comment in global.css says "the nav collapses to the docs link alone until phase 3 gives it a real disclosure control" — so it is phase 3's by assignment, not scope creep. It got a wrap rather than a disclosure, deliberately: with four links there is nothing to disclose, and a hamburger costs state, script and duplicate markup for no gain. The lockup keeps the first row, the links take the second, and it stays four keyboard stops. Flagging the deviation rather than making it quietly.

Things worth a second opinion

  • The SVG is aria-hidden. Not because it is decorative — it is the opposite — but because the four numbered steps beside it carry the same path in prose at real font sizes. A role="img" with a <desc> would make a screen reader read the path twice, and the second telling would be the worse one. The consequence is a rule: the diagram must never gain a fact the list does not have.
  • Docs links point at /docs/, not at a page inside the journey. The marketing routes are written in their final form (/features/, /modules/, /integrations/) and 404 until phases 4–6, which is what the header and footer have done since phase 1 and costs nothing given nothing deploys until phase 12. Documentation slugs are different — phases 7 and 8 own them, and a guessed one would be a stale URL nothing checks.
  • Not fixed, deliberately: astro check reports one pre-existing hint, an unused statSync import in scripts/applyBrand.mjs from phase 2. One word, in a file this PR does not otherwise touch — say the word and it goes in, otherwise it can ride with phase 10.

Verification

Every new check was negative-tested before being trusted, as in phases 1 and 2:

  • the coverage assertion, with a slug the page does not claim (pets) and with one the module dropped (cliloc) — both throw, with the right message
  • the demo-slot contract, with an attribute inserted between the pair and with the slot deleted — both fail, and the reported line number is the one in the file (comments are blanked rather than removed so offsets survive)
  • the capability fact, with platform.json edited to claim a ninth capability — FAIL, naming both sides

Then, on a clean checkout of this commit (git archive HEAD, npm ci, not the working tree):

  • checkTokens 22 files · checkBrand 19 /brand/ URLs · checkFacts 15 facts · astro check 0 errors · production build
  • live: / and /docs/ 200; every hero derivative resolves (X-Brand-Source: derived:default)
  • a demo URL mounted, applied, and reverted — the slot appears and disappears

And in a real browser at 1440px and in a 390px frame: no horizontal overflow at either, the long group takes both remaining slots on the top row so the five-group grid has no hole, and the diagram, cards and header all stack.


  • AI-assisted: written with Claude Code (Claude Opus 5).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz

Phase 3 of `PLAN.md` §13. Replaces phase 1's scaffold with the real homepage: hero, the data path as inline SVG, the self-hosted argument, all five capability groups, the get-started CTA, and the reserved demo slot. Branched from `main` after phase 2 (#5) merged. ## The three decisions, taken before any of it was written Recorded in `PLAN.md` §10 as **D17–D19**. **D17 — the data path is drawn generically, and captioned specifically.** The nodes read "your game server", "sidecar", "Runic Gateway", "browser and app", because §10 says a reader should not need to know this org's repository layout to understand the picture. It does not hide what ships: the sub-labels and the caption name ServUO and uo-link outright, since there is exactly one implementation of the shape today. **D18 — all five groups on the homepage, named only.** Not three with a link out: Integration and Infrastructure carry the module and self-hosted arguments, and hiding them until phase 4 would make the front page look smaller than the product. The per-capability argument stays `/features/`'s job, so there is one copy of it. **D19 — the hero leads with the emblem** rather than type alone. It costs what D16 already accepted — raster art a mounted `theme.css` cannot recolour — but every size is derived from whichever `logo.png` is in force (D14), so the hero, header, tab icon and installed icon still change together from one file. ## The capability list is now checked end to end `src/data/capabilities.mjs` holds the five groups as data, and every Game-intelligence item names the `module-uo` capability slug it comes from. The build fails if the page and `platform.json` disagree in either direction — an unlisted slug means the site is under-selling, an unknown one means it is advertising something that is gone (§1). Closing that loop needed **a fifteenth fact in `checkFacts.mjs`**. §12 names the capability list as an externally-sourced fact and nothing re-read it, so the whole chain rested on someone remembering. It is now `Module-uo` `main:module.json` → `capabilities`, sorted before comparing, so a reordered manifest is not a failure but an added or removed slug is. It immediately found something: **the site was omitting two of the module's eight capabilities.** `guilds` and `governors` were absent from §10's grouping. Both are real and documented (`shard_guilds`, `shard_governors`, `shard_governor_terms`), so they are now on the page and in `PLAN.md`. ## Two defects found by standing the build up, not by reading it **The demo slot's markup contract had no guard.** `applyBrand.mjs` reveals the demo link by replacing an exact pair of empty attributes in built HTML. An attribute inserted between them, or `href` written second, produces a build where the mount sets a demo URL, the boot log says nothing, and the link is simply never there. `checkBrand.mjs` now checks both halves — the slot exists, and nothing writes `data-demo-url` outside the exact pair — and derives the literal from the same expression `applyBrand.mjs` uses, so the two cannot drift. **The header nav overflowed on every phone.** Rendering the homepage in a 390px frame measured `scrollWidth` 433 against a 390px viewport: four links plus the lockup do not fit, so the whole page scrolled horizontally. This is pre-existing from phase 1, whose own comment in `global.css` says *"the nav collapses to the docs link alone until phase 3 gives it a real disclosure control"* — so it is phase 3's by assignment, not scope creep. **It got a wrap rather than a disclosure**, deliberately: with four links there is nothing to disclose, and a hamburger costs state, script and duplicate markup for no gain. The lockup keeps the first row, the links take the second, and it stays four keyboard stops. Flagging the deviation rather than making it quietly. ## Things worth a second opinion - **The SVG is `aria-hidden`.** Not because it is decorative — it is the opposite — but because the four numbered steps beside it carry the same path in prose at real font sizes. A `role="img"` with a `<desc>` would make a screen reader read the path twice, and the second telling would be the worse one. The consequence is a rule: the diagram must never gain a fact the list does not have. - **Docs links point at `/docs/`, not at a page inside the journey.** The marketing routes are written in their final form (`/features/`, `/modules/`, `/integrations/`) and 404 until phases 4–6, which is what the header and footer have done since phase 1 and costs nothing given nothing deploys until phase 12. Documentation slugs are different — phases 7 and 8 own them, and a guessed one would be a stale URL nothing checks. - **Not fixed, deliberately:** `astro check` reports one pre-existing hint, an unused `statSync` import in `scripts/applyBrand.mjs` from phase 2. One word, in a file this PR does not otherwise touch — say the word and it goes in, otherwise it can ride with phase 10. ## Verification Every new check was negative-tested before being trusted, as in phases 1 and 2: - the coverage assertion, with a slug the page does not claim (`pets`) and with one the module dropped (`cliloc`) — both throw, with the right message - the demo-slot contract, with an attribute inserted between the pair and with the slot deleted — both fail, and the reported line number is the one in the file (comments are blanked rather than removed so offsets survive) - the capability fact, with `platform.json` edited to claim a ninth capability — `FAIL`, naming both sides Then, **on a clean checkout of this commit** (`git archive HEAD`, `npm ci`, not the working tree): - `checkTokens` 22 files · `checkBrand` 19 `/brand/` URLs · `checkFacts` **15** facts · `astro check` 0 errors · production build - live: `/` and `/docs/` 200; every hero derivative resolves (`X-Brand-Source: derived:default`) - a demo URL mounted, applied, and reverted — the slot appears and disappears And in a real browser at 1440px and in a 390px frame: no horizontal overflow at either, the long group takes both remaining slots on the top row so the five-group grid has no hole, and the diagram, cards and header all stack. --- - [x] AI-assisted: written with Claude Code (Claude Opus 5). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz
wtclaude added 1 commit 2026-08-20 05:07:05 +00:00
feat(home): phase 3 — the homepage
All checks were successful
PR checks / checks (pull_request) Successful in 49s
556dee7355
Replaces phase 1's scaffold with the real homepage: hero, the data path as
inline SVG, the self-hosted argument, all five capability groups, and the
get-started CTA. Three decisions the org lead took first are recorded in
PLAN.md as D17-D19.

The data path is drawn generically and captioned specifically (D17): the nodes
say "your game server" and "sidecar", the sub-labels and caption name ServUO and
uo-link. The SVG is aria-hidden because the four numbered steps beside it carry
the same path in prose — one telling, not two.

The capability list is data with a check behind it (D18). Every Game-intelligence
item names the module-uo capability slug it comes from, and the build fails if
the page and platform.json disagree either way. That needed a fifteenth fact in
checkFacts.mjs: §12 named the capability list as an externally-sourced fact and
nothing re-read it, so the chain rested on someone remembering. It also found
that the site was omitting two of the module's eight capabilities — guilds and
city governors are now listed, in the page and in §10.

The hero leads with the emblem (D19), derived from whichever logo.png is in
force so one file still changes the hero, header, tab icon and app icon
together.

Also here, both found by standing the build up rather than by review:

  - checkBrand.mjs now enforces the demo slot's markup contract. applyBrand.mjs
    reveals the demo link by replacing an exact pair of empty attributes; an
    attribute inserted between them produces a build where the mount sets a demo
    URL, the boot log says nothing and the link never appears. Both halves are
    checked and the literal is derived from the expression applyBrand.mjs uses,
    so they cannot drift.

  - The header nav overflowed at 390px — four links plus the lockup measured
    433px against a 390px viewport, so every phone got a horizontally scrolling
    page. Phase 1 left this to phase 3 expecting a disclosure control; it got a
    wrap instead, because with four links there is nothing to disclose and a
    hamburger costs state, script and duplicate markup.

Verified on a clean checkout of this commit: all four checks, astro check, a
production build, a live /brand/* smoke, and a demo URL mounted and reverted.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit d9d7a8d47f into main 2026-08-20 05:14:02 +00:00
whitlocktech deleted branch feat/phase-3-homepage 2026-08-20 05:14:03 +00:00
Sign in to join this conversation.
No description provided.