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

forked from trevato/cs · proposes

better escape handling. on mac, it keeps minimizing my window #180

Merged opened this in answer to

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

Merged

Escape no longer costs the window. The cause is that Escape is the browser's key before it is the page's, and on macOS its meaning is *give the window back* — full screen drops out with the whole animation, mid-round — which no keydown handler can refuse. There is exactly one way to ask for the key instead of its meaning, and the new `src/engine/escape.ts` is the three lines of it: the Keyboard Lo…

shipped production is serving it deploy ae07c2d

opened this

Escape no longer costs the window. The cause is that Escape is the browser's key before it is the page's, and on macOS its meaning is give the window back — full screen drops out with the whole animation, mid-round — which no keydown handler can refuse. There is exactly one way to ask for the key instead of its meaning, and the new src/engine/escape.ts is the three lines of it: the Keyboard Lock API, asked for when the stage takes the screen and released when it loses it, held read live as the grant and still being full screen, and the press handed to the game whether or not the lock was granted. It knows no game — the window, the document and navigator.keyboard arrive as a host, and a Screen is anything with a live on, so a Fullscreen is one — and a browser without the API (Safari, Firefox) gets supported: false rather than a promise nobody kept.

In the shooter, Escape closes the buy menu if it is open and otherwise pauses: the panel up, the pointer let go — under the lock the browser does not do that for us, which is the point — and Resume focused. The panel's line says what the key actually cost on the browser it turned out to be: it kept the screen, or it took it and Resume will put it back. Resume is the gesture that does put it back, because the page now remembers the screen a player asked for rather than the one the browser is giving it. P pauses the same way and is a key no browser has a meaning of its own for, so it never costs the screen anywhere. At /range Escape gives the mouse back and keeps the screen where it can, and Capture the mouse takes back what a browser took.

Worth looking at in the preview: press <kbd>F</kbd>, then <kbd>Esc</kbd> — in Chrome or Edge the match pauses and you stay full screen (hold Escape to leave, which is the platform's own way out); in Safari the pause says the screen went and Resume brings it back in one click. /range is the same two paths. What I did not do: nothing forces full screen back without a gesture, because a browser will not honour that, and the world still simulates while paused — that is how this game already worked and is a separate change.

Tests: bun test, 781 pass. New behaviour has tests that fail without it — src/engine/escape.test.ts (nine, including the stale grant that must not outlive its screen), four in src/client/main.test.ts and one in src/range/boot.test.ts; the headless page grew FakeKeyboard and a keyboardLock: false option so the Safari path is driven, not assumed. No dependencies added; bundles rebuilt and under their ceilings (app 86 KB raw / 39 KB gzip).

🤖 Generated with Claude Code

Closes #178

commented

Merged main and pushed. The only conflict was in README.md, where both sides had added a paragraph at the same point in Playing: main's radar paragraph from #176/#179 and this branch's paragraph about Escape. Both are kept — the Escape paragraph first, since it continues the full-screen thread of the sentence above it, then the radar. Everything else auto-merged cleanly, and the two places where the branches touched the same lists came out with both meanings intact: CS_KEYS now carries KeyT: "flash" from main alongside KeyP: "pause", and the key hint on the game page reads G throw an HE · T throw a flashbang · … · Esc or P pause.

Bundles rebuilt after the merge (app.c520f46081.js, 90451 B raw / 40817 B gzip, inside main's raised ceiling), bun test is 803 pass / 0 fail, and the merged app boots with / answering in 6 ms and /range in 1 ms.

Escape is asked for, not obeyed: src/engine/escape.ts holds the key through the Keyboard Lock API while the stage has the screen, so in Chrome and Edge a pause is a pause and full screen stays; in Safari and Firefox the pause panel says the screen went and Resume — a gesture — puts it back, and P pauses without ever costing the screen. /range does the same, with Capture the mouse as the way back.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs