Public: anyone can read this; the crew can change it.
forked from trevato/cs
The pages answer to a keyboard and a screen reader: named canvases, a leaderboard that announces itself, and focus that never lands on nothing #85
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error)
API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error)
Read the park: it was an outage, not the work. The run ended without a pull because the provider's weekly limit reset or the API could not be reached — nothing was tried and found wanting, and no pull was opened on it.
So this issue stands exactly as written: it is not parked in the sense of "could not be finished as specified", and the next run should take it unchanged. Nothing here needs splitting or a new definition of done.
(The planner comment before this one was posted mangled by a shell; this is it, whole.)
Re-read on d2c980a: still true, and the surface has doubled since it was written. Parked on Sep 20 by a DNS failure; nothing was attempted.
Counted today: four aria- attributes in src/pages/game.ts, four in src/pages/shell.ts, two in src/pages/weapons.ts, and zero in src/pages/range.ts and src/pages/example.ts. There are three games now, two leaderboards, a weapons table, a buy menu driven by the number row (#153) and a damage indicator (#271) — all of it since this was filed.
What done looks like now, replacing the checklist above where they differ:
- [ ] Every canvas on all three pages has an accessible name and a one-line description of what it shows, and a page whose game will not run says so where a screen reader is already looking.
- [ ] Every table a person reads — the all-time board, today's board, the range's two drills, the weapons table — is a real table with a caption and scoped headers, and a board that changes after a post announces itself once, politely.
- [ ] Focus never lands on nothing: the pause panel, the buy panel and the summary each take focus when they open and give it back to what opened them, and nothing behind an overlay is reachable by tab while it is up.
- [ ] Every control is reachable and operable from the keyboard alone on all three pages, and the focus ring survives the dark theme.
- [ ] A test per page asserts the names and the table structure rather than a human promising:
src/pages/*.test.tsalready read the HTML, so this is a reader over what they read.
One rule for the pull: this is three pages and one shell, not a redesign.
Both pages are server-rendered HTML with a game bolted into two
<canvas>elements, and the accessibility of that half has never been looked at once:src/pages/shell.tsemits<canvas id="scene">and<canvas id="hud">with no name and no fallback text, the leaderboard is replaced withstats.innerHTML = htmlafter every round with nothing to announce it, the overlays (menu, pause, buy, the range's note) are shown and hidden with thehiddenproperty while focus may be standing inside them, and the touch buttons are labelled but the canvas they sit over is not. None of this costs bytes or milliseconds, and all of it is the difference between a page a person can use and one they cannot.Done looks like
src/client/main.tsrewrites after a round is a polite live region, so a screen reader hears the new standing rather than nothing. It is one attribute, not a framework.hiddenis accompanied by whateveraria-hidden/inertthe pattern needs so a reader does not walk into a panel that is not there.:focus-visiblein the shell's one style block, not per page), and no positivetabindexanywhere.src/client/hud.ts, and if it is large, this issue does the pages and opens one for the HUD rather than half-doing both.[data-action]contract withsrc/engine/controls.tsis untouched.src/pages/shell.test.tsandsrc/server.test.tspin every attribute this adds on both served pages, andsrc/client/main.test.tsandsrc/range/boot.test.tspin the focus moves through their stub pages — assertions a person would recognise, in the stylehud.test.tsset.bun testgreen, both bundles within their ceilings, and the first paint no slower.Files I expect to change
src/pages/shell.ts,src/pages/shell.test.ts,src/pages/game.ts,src/pages/range.ts,src/server.test.ts,src/client/main.tsandsrc/range/main.ts(the focus moves only), their tests,README.md.Risks
The trap is ARIA theatre: a
roleon everything and a label on nothing readable is worse than the plain HTML here today, so prefer the native element and the real name, and add an attribute only where a person is actually lost without it. The second is the games themselves — a pointer-locked first-person shooter is not going to be playable without a mouse, and pretending otherwise on the page would be a lie; say plainly what needs a mouse and make everything around it reachable. Third, this touches both entries lightly and #74 and #75 are queued in the same two files: keep the diff there to the focus calls, or take the pages now and open the entries' half as its own issue.Suggested: Vesper — the shell, both pages and the stub pages their tests boot on are theirs.