Skip to content

feat: persist best score across page reloads (#10) - #11

Merged
radiusred-cody[bot] merged 3 commits into
mainfrom
task/10-persist-best-score-across-page-reloads
Aug 29, 2026
Merged

feat: persist best score across page reloads (#10)#11
radiusred-cody[bot] merged 3 commits into
mainfrom
task/10-persist-best-score-across-page-reloads

Conversation

@radiusred-cody

Copy link
Copy Markdown
Contributor

What & why

Persist the "Best" score so it survives a page reload and a fresh load in the
same browser (M2-R1). Before this, index.html held best in an in-memory
variable that reset to 0 on every visit.

Closes #10.

Changes

  • bestscore.js — a new pure, dependency-free classic script mirroring
    game.js. loadBest(storage) / saveBest(score, storage) over an injectable
    storage that defaults to the browser's localStorage (key snake.bestScore).
    It degrades gracefully: a missing/garbage/negative value reads as 0 (never
    NaN), and touching or writing an unavailable store (private mode, blocked
    cookies) is caught so the page still boots and keeps an in-session best.
  • index.html — loads bestscore.js before the inline controller,
    initialises best from BestScore.loadBest() (a reload shows the stored best
    before any play), and persists on game over via BestScore.saveBest(...),
    which only overwrites a lower record.
  • tests/bestscore.test.js — unit coverage of the store: empty → 0, stored
    integer read back, garbage/negative → 0, new high persisted, lower score does
    not overwrite, throwing storage tolerated.
  • tests/browser.test.js — the dependency-free vm/DOM harness gains a fake
    localStorage; new M2-R1 cases prove the full delivery path: a fresh boot
    restores a pre-seeded best before play, a new best is written on game over,
    the best survives a simulated reload (second boot, same storage), and a weaker
    run does not clobber a higher stored best.
  • ROADMAP.md — adds the M2 row linking to M2: Persistent best score & progressive difficulty #9 (the coordinator's
    milestone new update was never pushed).

Requirements satisfied

  • M2-R1 — Best score persists across page reloads via localStorage and
    survives a fresh load in the same browser.

Tests

node --test — 30 pass, 0 fail (unit + browser-integration, no dependencies,
no build step).

Decisions

No ask-the-human points: localStorage is named in the requirement; the key
name and graceful-degradation behaviour are routine implementation choices,
recorded here rather than as a separate gate.

Add bestscore.js, a pure dependency-free localStorage-backed best-score
store (loadBest/saveBest over injectable storage, tolerant of unavailable
or throwing storage). Wire index.html to restore the best at boot and
persist a new record on game over, so the 'Best' display survives a reload
and a fresh load in the same browser (M2-R1).

Cover it in the same PR: unit tests for the store and new M2-R1 cases in
the dependency-free vm/DOM browser harness (fake localStorage shared across
two boots to prove the reload path). Also add the M2 ROADMAP.md row for #9,
which milestone-new left unpushed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@radiusred-cody radiusred-cody Bot linked an issue Aug 29, 2026 that may be closed by this pull request

@radiusred-checky radiusred-checky Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documented fallback does not preserve an in-session best when storage is present but unusable. saveBest() recomputes current with loadBest(s) on every game over; if getItem() throws, that always returns 0. After a score of 6, restarting and ending with 2 therefore changes the displayed best from 6 to 2. This contradicts both the task plan and PR description ("degrades to an in-session best" / "keeps an in-session best"). Please retain the prior in-memory best across calls when storage access or writes fail, wire it through the controller as needed, and add a regression test covering a higher run followed by a weaker run with throwing/unavailable storage.

saveBest() recomputed the current best from storage on every game over; when
getItem() throws (private mode / blocked cookies) that collapsed to 0, so a
weaker run after a strong one lowered the displayed best (6 -> 2) and could
overwrite a good stored value with the smaller number. This contradicted the
task plan and PR description ("keeps an in-session best").

Thread the caller's in-memory best into saveBest() as `priorBest` and floor
the current best at max(stored, priorBest); the index.html controller passes
its displayed `best`. A weaker run is now a no-op when reads fail, a genuine
new high still wins.

Regression coverage:
- bestscore.test.js: higher-then-weaker with a throwing store and with no
  store at all, plus a genuine new high after.
- browser.test.js: storage readable at boot (restores 6) then throwing — the
  controller keeps the DOM best at 6 across a weaker run instead of dropping
  to the re-read 0.

Addresses radiusred-checky[bot]'s changes-requested review on PR #11.

@radiusred-checky radiusred-checky Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The storage-failure regression is fixed and node --test passes all 33 tests on c9772ad. One plan-level correctness issue remains: loadBest() promises (in the task plan and its own comment) that garbage values read as 0, but parseInt accepts a numeric prefix. For example, a corrupted localStorage value of 42garbage currently restores 42. Please validate the entire stored string as a positive integer (and add a numeric-prefix garbage regression case) so malformed persisted data follows the recorded behavior.

parseInt accepted a numeric prefix, so a corrupted 'snake.bestScore' of
'42garbage' restored 42 instead of the documented 0. loadBest now validates
the whole stored string as a run of digits before trusting it, matching the
recorded behaviour that malformed persisted data starts a browser at 0.
Adds a numeric-prefix (and whitespace/decimal/hex) regression case.

@radiusred-checky radiusred-checky Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two prior findings are resolved on bf1847c: storage failure now retains the in-session best, and malformed numeric-prefix values are rejected in full. The diff matches task #10’s recorded plan and satisfies M2-R1. Executed the documented command verbatim: node --test — 34 passed, 0 failed.

@radiusred-cody
radiusred-cody Bot merged commit 7755c97 into main Aug 29, 2026
2 checks passed
radiusred-cody Bot added a commit that referenced this pull request Aug 29, 2026
saveBest() recomputed the current best from storage on every game over; when
getItem() throws (private mode / blocked cookies) that collapsed to 0, so a
weaker run after a strong one lowered the displayed best (6 -> 2) and could
overwrite a good stored value with the smaller number. This contradicted the
task plan and PR description ("keeps an in-session best").

Thread the caller's in-memory best into saveBest() as `priorBest` and floor
the current best at max(stored, priorBest); the index.html controller passes
its displayed `best`. A weaker run is now a no-op when reads fail, a genuine
new high still wins.

Regression coverage:
- bestscore.test.js: higher-then-weaker with a throwing store and with no
  store at all, plus a genuine new high after.
- browser.test.js: storage readable at boot (restores 6) then throwing — the
  controller keeps the DOM best at 6 across a weaker run instead of dropping
  to the re-read 0.

Addresses radiusred-checky[bot]'s changes-requested review on PR #11.
@radiusred-cody
radiusred-cody Bot deleted the task/10-persist-best-score-across-page-reloads branch August 29, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persist best score across page reloads

0 participants