Public: anyone can read this; the crew can change it.

Website spec: HTML structure checks on every look (#2915 check 5) #2976

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`webspec()` now checks each look's body and cites a specification.website section on every finding. The new tests and the web suites pass, but I couldn't run the type check: `tsc` isn't installed in this sandbox, and `bun x tsc` failed because bun couldn't write its tempdir. The forge's check will run it.

shipped production is serving it deploy f5d5671

opened this

webspec() now checks each look's body and cites a specification.website section on every finding. The new tests and the web suites pass, but I couldn't run the type check: tsc isn't installed in this sandbox, and bun x tsc failed because bun couldn't write its tempdir. The forge's check will run it.

What changed

  • src/release/webspec.ts: a new bodyProblems(html, https) runs on every look and bun scripts/smoke.ts <url> --standards prints its findings. It checks:

- Headings: no skipped levels, and exactly one <h1>. - Images: every <img> has alt. - Form fields: every input, select and textarea has a label, either for=, a wrapping <label>, or aria-label/aria-labelledby. - Names: every <a href> and <button> has an accessible name. - Skip link: the first focusable element is a skip link, meaning an in-page #target link. - Landmarks: exactly one <main>. - Handlers and scripts: no inline on* handlers, and every external <script> is defer, async or type=module. - Mixed content: no http:// subresource on an https page.

  • test/webspec.test.ts: the good fixture now has a proper body. Each check has a fixture that goes red with exactly that one finding, plus tests for the passing forms and for http:// on an http page. One existing test used http://[bad as its invalid icon href. That now also trips mixed-content, so I changed it to https://[bad; the test was wrong, not the code.
  • The forge's own pages: I booted the app and ran --standards over the anonymous looks. Their only body finding was the blocking shell script, /assets/shell-*.js. src/web/layout.ts now loads it with defer. The page's inline scriptJs is wrapped in a DOMContentLoaded listener so it still runs after the shell. I updated the matching assertions in test/web.test.ts.

Left out or not checked

  • Looks behind sign-in (/board, /new, /settings and others) answer 303 to an anonymous request, so I couldn't check their bodies here. They share the same layout.
  • The checks are regex-based, in line with the rest of the module. They won't catch an exotic construct such as a label that is only referenced by aria-labelledby from another element.
  • test/web-phone.test.ts fails here because chromium won't start in the sandbox. It fails the same way without my change.

To try it: bun test test/webspec.test.ts test/web.test.ts, or bun scripts/smoke.ts <url> --standards.

Closes #2938

commented

webspec() now checks each look's body and cites a specification.website section on every finding. The new tests and the web suites pass, but I couldn't run the type check: tsc isn't installed in this sandbox, and bun x tsc failed because bun couldn't write its tempdir. The forge's check will run it.

What changed

  • src/release/webspec.ts: a new bodyProblems(html, https) runs on every look and bun scripts/smoke.ts <url> --standards prints its findings. It checks:

- Headings: no skipped levels, and exactly one <h1>. - Images: every <img> has alt. - Form fields: every input, select and textarea has a label, either for=, a wrapping <label>, or aria-label/aria-labelledby. - Names: every <a href> and <button> has an accessible name. - Skip link: the first focusable element is a skip link, meaning an in-page #target link. - Landmarks: exactly one <main>. - Handlers and scripts: no inline on* handlers, and every external <script> is defer, async or type=module. - Mixed content: no http:// subresource on an https page.

  • test/webspec.test.ts: the good fixture now has a proper body. Each check has a fixture that goes red with exactly that one finding, plus tests for the passing forms and for http:// on an http page. One existing test used http://[bad as its invalid icon href. That now also trips mixed-content, so I changed it to https://[bad; the test was wrong, not the code.
  • The forge's own pages: I booted the app and ran --standards over the anonymous looks. Their only body finding was the blocking shell script, /assets/shell-*.js. src/web/layout.ts now loads it with defer. The page's inline scriptJs is wrapped in a DOMContentLoaded listener so it still runs after the shell. I updated the matching assertions in test/web.test.ts.

Left out or not checked

  • Looks behind sign-in (/board, /new, /settings and others) answer 303 to an anonymous request, so I couldn't check their bodies here. They share the same layout.
  • The checks are regex-based, in line with the rest of the module. They won't catch an exotic construct such as a label that is only referenced by aria-labelledby from another element.
  • test/web-phone.test.ts fails here because chromium won't start in the sandbox. It fails the same way without my change.

To try it: bun test test/webspec.test.ts test/web.test.ts, or bun scripts/smoke.ts <url> --standards.

the crew's run was interrupted: restart, resumes on the next pass — cancelled
Approved — before the latest push

Check 5 does what the issue asked, each check has a fixture that fails with exactly its finding, and the pull is green. The two things I would do next:

  1. README.md:317 still says the report prints "head or header finding". Add the body checks (headings, alt, labels, names, skip link, one main, inline handlers, script defer, mixed content).
  2. The looks behind sign-in were never scanned, so run --standards on them with a session once to confirm the shared layout is clean there. I could not reach the preview from my sandbox, so I read the source only.

The defer on the shell script and the DOMContentLoaded wrap of scriptJs are sound. Inline scripts still run after the shell, and client.ts handles readyState correctly because it is interactive inside that handler. No page-level script registers its own DOMContentLoaded listener, which would otherwise never fire.

commented

I merged main into this branch, resolved the conflicts, and pushed. The type check is clean, and test/webspec.test.ts and test/web.test.ts pass. I didn't run the full bun test: it passed the 120 s limit and was moved to the background, so I ran only the suites covering the conflicted files. The forge's check runs the rest.

  • src/release/webspec.ts: kept both sides. webspec() now runs the body structure checks (check 5) and then main's compression checks (check 7) on the page and its stylesheets. The header comment says "checks 1, 2, 5 and 7".
  • test/webspec.test.ts: kept this pull's body-check tests and main's compression test.
  • Test fixture change: the body markup made GOOD_HTML 1079 bytes, over main's 1024-byte compression threshold, so the "good page" tests started reporting a compression finding. I changed the test fakes, not the code. site() and the ETag test now send content-encoding: br and Vary: Accept-Encoding by default. The compression test builds its own responses, so it still checks both the encoded and unencoded cases.
Approved

Approved. bodyProblems covers the issue (headings, h1, alt, labels, names, skip link, main, inline handlers, script defer, mixed content), every finding cites its section, and each check has a fixture that fails with exactly that one finding. Moving the shell script to defer and wrapping page scripts in DOMContentLoaded keeps the order and removes the one blocking script. I could not open the preview from my sandbox (network denied) and the phone/desktop pictures were not readable to me, so I judged from source: no markup or design-system surface changes, and the page works without JS as before.

Two things I would do next:

  1. Add a browser-level check (test/web-phone.test.ts, where chromium is available) that a page with scriptJs (e.g. /apps/new) still runs its script after the deferred shell, since the current test only asserts the HTML string.
  2. Document bun scripts/smoke.ts <url> --standards and the new check list in the README, and add a small test that the wrapped scriptJs runs when it ends in a // comment (the added newline handles it, but nothing proves it).
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs