Something went wrong. Try again.
A Go-like game designed to be playe by two-to-eight players competing for territory!
Something went wrong. Try again.
1.9 kB
Markdown
at main
Window-level input guards #
attachEndTurn in ui.ts binds its Enter/Space handler to window, not to
the End Turn button. That one decision has now produced three separate bugs,
each found after the guard was believed correct:
- 2026-09-06 —
button.disabledsuppresses the button's own click but has no effect on a window listener, so Enter/Space kept ending turns after a win. Fixed by moving the real legality check intogame.ts. - 2026-09-07 — adding a
<select>meant Space (which opens the dropdown) also ended the turn. Fixed by widening the guard from "is the End Turn button focused" to "is any form control focused." - 2026-09-08 — the concede
<dialog>.showModal()makes the background content inert (pointer events, focus, tab order) but has NO effect on a listener bound towindow: a keydown inside the dialog still bubbles through the dialog todocumenttowindow. Clicking the dialog's non-focusable body text moved focus off Cancel onto the<dialog>element, defeating the form-control check, so Enter ended the turn behind the open modal — and because the dialog's player name is set once at open time, confirming afterwards would have conceded a different player than the one named. Fixed by bailing on any open dialog.
Two rules follow:
- Modal inertness is not listener protection. Nothing about
showModal(),inert, orpointer-events: nonestops an event from reaching awindow/documentlistener. Guard explicitly. - A guard on a window-level listener is a claim, and it needs a dispatched
event behind it, never code inspection. Assert on the event's own
defaultPrevented(see the technique bullet inbuild-and-serve.md) plus the state that must not have changed. All three bugs above passed a reading of the code; two of them were caught only by dispatching, and the third by predicting the dispatch would fail.