ReviewOS

also looking at this

stacks/lastsoulultra

LSU's own coaching surface, and what auditing the site turned up

#12
Merged glennmichael123 wants to merge claude/last-soul-ultra-web-6e0718 into main
86 files +3943 -1190
6 days ago

LSU now renders its own coaching experience over HQ instead of only framing it: LSU chrome, LSU words, LSU red and black. Auditing that work turned up a run of unrelated defects on the existing site, which make up most of the rest of the branch.

The coaching surface

Eight module routes under /coaching, one per HQ training module, each with this site's chrome around HQ's panel. The palette is the site's own tokens, which are also what the HQ brand preset publishes (#000, #0d0d0f, #dd0f15, Anton over Barlow, square corners), held in CSS where it cannot depend on a request completing.

  • The storefront is ours. The published brand is public, so the tiers, the coach behind each one, what each includes and the coach's own price are rendered here rather than only inside a cross-origin frame. A search result, a shared link and a visitor whose browser blocks third-party frames all get the same answer. Nothing is composed: no price, trial or coach name originates on this site, and with nothing published the page says exactly that.
  • One endpoint. GET /api/brands/<slug> is the only coaching call this site may make; everything else is account-scoped and its tokens never cross the embedding boundary. parseBrand checks every field, keeps a javascript: or relative URL out of an href, drops one malformed offer rather than the whole storefront, and refuses a payload published under a different slug. The fetch is bounded at 2.5s, never throws, is cached for a minute, and single-flights.
  • Scopes said before the click. health and messages read the athlete's own account-scoped HQ endpoints, so a brand never sees them, and the pages say so rather than letting a runner assume the race can read them.
  • The draft state is the shipping state. With nothing published, a module frames nothing, loads no remote script, warms no connection, and says it is not open yet with the way back to the hub.
  • Permissions-Policy: microphone is delegated on the two routes that frame feedback. Only the local preview server did it before, so recording would have been blocked inside HQ's frame in production, silently.

Fixes found while auditing

  • /lottery — the section the entry page calls #form held a placeholder reading "Loading account access…", hooked by an attribute nothing in the repo reads, with the real form a thousand pixels below it. Its entry steps and 2027 event list used fourteen class names defined in no stylesheet and rendered as plain body text.
  • /404 — declared pageTitle, pageDescription and noindex as loose names the head never reads, so it served the site's default title and invited indexing. It was also written in six more classes that do not exist, and wrote its own html/head/body, making it the one page with no skip link, no focusable main and the wrong lang.
  • Share cards — nine 1200x630 cards shipped, recorded in asset-integrity.json, and were never referenced; every page advertised one portrait WebP. twitter:image was missing entirely.
  • /faq was a byte-identical copy of /contact that canonicalised to it, and was in the sitemap anyway.
  • .h3 was used by the results tables and defined nowhere.
  • Duplicate tabindex on the home carousel; aria-current missing site-wide; contrast below AA in four rules this branch had added.

Performance

/world-series carried @ts-maps inline: 844 KB of JavaScript in the first 1% of the document, parsed before anything painted and re-downloaded every visit. A post-build step lifts page-local module bundles into /_stx/modules.<hash>.js, the shape stx's own SPA router already recognises. That page is 34 KB now; 983 KB left HTML across three pages. Separately, stx-site.css — the one sheet every route downloads — went from 11.5 KB to 2.5 KB.

Checked, not assumed

scripts/audit-html.ts grew from nothing to thirteen rules, each with a page it must complain about and a page it must not. The same rules run over the rendered views in bun test, so a regression fails the suite without a build. Every race figure on both results pages is recomputed from results-<year>.json and matched inside that season's own section.

165 tests, lint, typecheck and the audit pass. The globe, the 160-row results table and the coaching surface were each checked in a browser rather than by byte count.

Deliberately not done

  • The privacy policy says this site "embeds nothing" while /coaching and eight module routes embed hq.training. Binding text; Kim and Alexander's to change.
  • Em-dashes across the captured production copy are the organiser's published wording, not rewritten here.
  • Two dormant features — the live scoreboard (/api/live is served, nothing fetches it) and the double opt-in newsletter (complete backend, no signup form) — are documented in the README rather than rebuilt, since both were lost the same way and nothing recorded them.

🤖 Generated with Claude Code

86 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.