- app.js: pause/pagehide listeners passed the Event into savePos(floor),
making pos NaN -> serialized as null -> every pause/unload clobbered the
saved position. Wrap in arrows; pagehide now posts keepalive so the final
save survives page unload
- app.js: play() re-fetches history on a miss (boot fetch swallows errors)
and only bumps an existing row instead of posting pos:0 at start-of-play
- app.py: POST /api/history requires slug_id (was stored as "None"),
validates/clamps pos; upsert keeps the old payload when the incoming
pos is null so stale clients can't corrupt a row
- tests: +3 API tests (slug required, pos clamping, null-pos keeps saved),
+1 end-to-end browser test (resume ~400s with a failed boot fetch);
lockout test now uses a throwaway username so it can't lock out users
the browser tests log in with; hardened the flaky autoplay test