mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-05-06 09:38:33 -05:00
[PR #1430] [MERGED] fix(staffml): stale closure in gauntlet keyboard handler and importProgress partial-write #9099
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/harvard-edge/cs249r_book/pull/1430
Author: @Shashank-Tripathi-07
Created: 4/22/2026
Status: ✅ Merged
Merged: 4/22/2026
Merged by: @profvjreddi
Base:
dev← Head:fix/staffml-stale-closure-and-import-rollback📝 Commits (1)
08cb905fix(staffml): stale closure in gauntlet keyboard handler and importProgress partial-write📊 Changes
2 files changed (+55 additions, -40 deletions)
View changed files
📝
interviews/staffml/src/app/gauntlet/page.tsx(+22 -21)📝
interviews/staffml/src/lib/progress.ts(+33 -19)📄 Description
What this fixes
Two independent bugs in StaffML that silently corrupt user state under specific but realistic conditions.
Bug 1: Stale closure in gauntlet keyboard shortcuts
File:
interviews/staffml/src/app/gauntlet/page.tsxscoreAndNextwas a plain function (not wrapped inuseCallback). The keyboard shortcutuseEffecthad a dependency array of[phase, showAnswer], which meant the event handler captured stale values ofcurrentIdxandscoresfrom the render in which the effect last ran.Repro: open a gauntlet, answer Q1 with the mouse (state updates), then press key
2to score Q2. The handler fires withcurrentIdx = 0still captured, so Q1 gets scored a second time and Q2 is skipped entirely.Fix: wrap
scoreAndNextinuseCallbackwith its actual deps (questions,currentIdx,scores,timeRemaining,selectedDuration,selectedTrack,selectedLevel), add it to the keyboard effect's dependency array, and move the effect below thescoreAndNextdeclaration to satisfy TypeScript's temporal dead zone check.Bug 2: importProgress partial-write with no rollback
File:
interviews/staffml/src/lib/progress.tsimportProgressvalidated and wrote keys tolocalStorageone at a time. Ifattemptspassed validation and was written, thengauntletsfailed itsArray.isArraycheck, the function returnedfalsebutSTORAGE_KEYalready held the new attempts data whileGAUNTLET_KEYstill held the old gauntlet history. The user ends up with a silently inconsistent state: a mix of new and old data that skews cross-session analytics.The same class of failure applies to
QuotaExceededError(private browsing mode with limited quota) mid-write.Fix: validate all shapes first before touching storage, snapshot all relevant
localStoragekeys upfront, and restore the snapshot if any write throws.Verification
Both changes pass
tsc --noEmitwith zero errors. No new dependencies introduced.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.