v0.7.8 — the setup screen was unreachable for anyone who had ever played

Third report of the same symptom, this time with the build confirmed
current on screen, which ruled out v0.7.7's caching fault and left the
real cause exposed.

v0.7.5 skipped the setup screen whenever load() found a save, reasoned
as "a saved game is a game to resume". A browser that has ever played
solitaire always has one, so the door could never reach the screen
again — and the fresh private window that appeared to vindicate v0.7.7
simply had no save. Two real faults were stacked; the caching one is
fixed and had been masking this.

The door outranks a saved game now: ?solitaire is a request to set one
up, while a bare reload still resumes (pinned by its own test). Since
Deal clears the save, the screen carries #ss-resume and says what Deal
costs, so the door cannot destroy a game in progress.

Also, per Jesse, riding along rather than taking its own release: the
splash footer now names both ways to play.

868 tests pass, four new. The reproduction was a failing test written
before the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdG46Ja2PEDBkpqiDazMoX
This commit is contained in:
Jesse.Markowitz
2026-08-29 23:47:44 -04:00
co-authored by Claude Sonnet 5
parent af68aac78d
commit 193800a649
7 changed files with 142 additions and 11 deletions
+43
View File
@@ -4192,6 +4192,49 @@ describe('the New Game dialog', () => {
assert.equal(els.get('gameui')!['hidden'], true, 'a game was dealt before anyone chose anything');
});
it('the solitaire door reaches the setup screen even when a solitaire game is saved', async () => {
/**
* REPORTED THREE TIMES BY JESSE (2026-08-29, twice, and 2026-08-30). v0.7.5 skipped the setup
* screen whenever `load()` found a save, reasoned as "a saved game is a game to resume" — but
* that means ANY browser that has ever played solitaire can never reach the setup screen from
* the door again, which is the whole feature. The private window that appeared to prove the
* caching fix had simply never played, so its `localStorage` was empty.
*
* The door is an explicit request to set a game up. A BARE reload still resumes (below).
*/
const save = JSON.stringify({ seed: 12345, history: [] });
const { els } = await load('?solitaire', { 'station-master.save.v1': save });
assert.equal(els.get('solitairesetup')!['hidden'], false, 'a saved game swallowed the door');
assert.equal(els.get('gameui')!['hidden'], true, 'the saved game was resumed instead of asking');
});
it('offers a way back to the saved game, since dealing from the door destroys it', async () => {
// Deal calls `clearSave()`. The door is reached by clicking "Play solitaire", which nobody reads
// as "discard what I was playing" — so the save has to be one button away, and the cost of Deal
// has to be stated. Resuming navigates to the bare URL and lets `start()` do it.
const save = JSON.stringify({ seed: 12345, history: [] });
const { els, nav } = await load('?solitaire', { 'station-master.save.v1': save });
assert.equal(els.get('ss-resume')!['hidden'], false, 'no way back to the game in progress');
assert.equal(els.get('ss-saved-note')!['hidden'], false, "Deal's cost to the save is not stated");
(els.get('ss-resume')!['onclick'] as () => void)();
assert.equal(nav.search, '', 'resuming did not go back to the plain resume path');
});
it('hides the resume button when there is no saved game to go back to', async () => {
const { els } = await load('?solitaire');
assert.equal(els.get('ss-resume')!['hidden'], true, 'a resume button with nothing to resume');
assert.equal(els.get('ss-saved-note')!['hidden'], true, 'warns about replacing a save that does not exist');
});
it('a bare reload still resumes a saved solitaire game rather than asking again', async () => {
// The other half: `?solitaire` is what changed, not resuming itself. Reopening the tab must not
// put a question in front of somebody who just wants their game back (D11's zero-friction case).
const save = JSON.stringify({ seed: 12345, history: [] });
const { els } = await load('', { 'station-master.save.v1': save });
assert.equal(els.get('gameui')!['hidden'], false, 'a bare reload did not resume the saved game');
assert.equal(els.get('solitairesetup')!['hidden'], true, 'the setup screen interrupted a resume');
});
it('the solitaire door reaches solitaire even when this browser remembers a multiplayer game', async () => {
// Found 2026-08-29 verifying v0.7.5 on phoenix.local: a browser with ANY remembered multiplayer
// seat (`station-master.remote.v1`) could never reach solitaire's setup screen at all — a bare