Ask before leaving the page while a world is open (Ctrl+W closed the game)
Ctrl (replace) + W (fly) hits the browser's close-tab shortcut, which a page cannot block. A beforeunload prompt now catches it. Autosave on hiding the tab no longer depends on the audio context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,21 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
|||||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||||
open or not verified. Keep each entry short; the git history has the details.
|
open or not verified. Keep each entry short; the git history has the details.
|
||||||
|
|
||||||
|
## 2026-10-23: Ctrl+W no longer closes the game without asking (Claude Code)
|
||||||
|
|
||||||
|
Note: "crash: hoplding crtl and pressing W crashes the whole browser."
|
||||||
|
|
||||||
|
- Cause: Ctrl+W is the browser's close-tab shortcut (the whole window if it is the last tab);
|
||||||
|
holding Ctrl for replace while pressing W to fly hits it. Chrome and Brave reserve it, so the
|
||||||
|
page never sees the key and cannot cancel it.
|
||||||
|
- `src/ui/input.js`: a `beforeunload` handler makes the browser ask before leaving while
|
||||||
|
`GAME.running`. The `visibilitychange` autosave no longer depends on the audio context
|
||||||
|
existing (it used to return early before any sound had played).
|
||||||
|
- `test/play.py`: 2 checks (leaving asks while a world is open, not on the title screen); 120
|
||||||
|
checks pass. All 11 headless suites pass; `t_smooth.py` passed on its rerun (the known flaky
|
||||||
|
half-resolution check failed once at load ~5).
|
||||||
|
- Open: the real Ctrl+W prompt can only be seen in a headed browser; the owner checks it.
|
||||||
|
|
||||||
## 2026-10-23: build menu hover popup, top-left card hidden while building (Claude Code, with Codex)
|
## 2026-10-23: build menu hover popup, top-left card hidden while building (Claude Code, with Codex)
|
||||||
|
|
||||||
Notes: "mouse hover reviels a new info popup…" and "dont need the short description in the build
|
Notes: "mouse hover reviels a new info popup…" and "dont need the short description in the build
|
||||||
|
|||||||
+5
-1
@@ -69,7 +69,11 @@ canvas.addEventListener('wheel', e => {
|
|||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
window.addEventListener('resize', () => { if (R.canvas) resizeRender(); });
|
window.addEventListener('resize', () => { if (R.canvas) resizeRender(); });
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.hidden && GAME.running) saveWorld('auto', true);
|
||||||
if (!AU.ctx) return;
|
if (!AU.ctx) return;
|
||||||
if (document.hidden) { if (SET.muteUnfocused) AU.ctx.suspend(); if (GAME.running) saveWorld('auto', true); }
|
if (document.hidden) { if (SET.muteUnfocused) AU.ctx.suspend(); }
|
||||||
else AU.ctx.resume();
|
else AU.ctx.resume();
|
||||||
});
|
});
|
||||||
|
// Ctrl+W closes the tab and the page cannot stop it, and Ctrl (replace) + W (fly) is an easy slip:
|
||||||
|
// while a world is open, ask the browser to confirm leaving
|
||||||
|
window.addEventListener('beforeunload', e => { if (GAME.running) { e.preventDefault(); e.returnValue = ''; } });
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ with sync_playwright() as p:
|
|||||||
ev = pg.evaluate
|
ev = pg.evaluate
|
||||||
look = sys.argv[2] if len(sys.argv) > 2 else 'pixel'
|
look = sys.argv[2] if len(sys.argv) > 2 else 'pixel'
|
||||||
check('a new game starts fully zoomed in', ev('SET.zoom') == 12, ev('SET.zoom'))
|
check('a new game starts fully zoomed in', ev('SET.zoom') == 12, ev('SET.zoom'))
|
||||||
|
# Ctrl+W (Ctrl to replace, W to fly) closes the tab; with a world open the browser must ask first
|
||||||
|
leave = "(() => { const e = new Event('beforeunload', { cancelable: true }); dispatchEvent(e); return e.defaultPrevented; })()"
|
||||||
|
check('leaving the page with a world open asks to confirm', ev(leave))
|
||||||
|
check('but not on the title screen', not ev(f'(() => {{ GAME.running = false; const r = {leave}; GAME.running = true; return r; }})()'))
|
||||||
ev("SET.zoom = 4; resizeRender(); R.snapNext = true;") # the rest of this play-through was laid out at zoom 4
|
ev("SET.zoom = 4; resizeRender(); R.snapNext = true;") # the rest of this play-through was laid out at zoom 4
|
||||||
ev(f"SET.look = '{look}'"); pg.wait_for_timeout(200)
|
ev(f"SET.look = '{look}'"); pg.wait_for_timeout(200)
|
||||||
check(f'the {look} look is drawing', ev('R.smoothOn') == (look == 'smooth'), ev('[SET.look, R.smoothOn, SM.ok]'))
|
check(f'the {look} look is drawing', ev('R.smoothOn') == (look == 'smooth'), ev('[SET.look, R.smoothOn, SM.ok]'))
|
||||||
|
|||||||
Reference in New Issue
Block a user