v0.3.1 - interval clear on load + audio-toggle hoist + electron dev script fix
Scene Simulator v0.3.1 — Interval Clear on Load + Audio-Toggle Hoist + Electron Dev Script Fix
Release date: 2026-07-08
Compare: v0.3.0...v0.3.1
Commits on this tag: 1 (the chore(release): cut v0.3.1 version-bump commit containing 3 grouped bug fixes).
This is a maintenance branch resolving three edge-case bugs spanning the Electron development environment, the React component-tree boundary, and the event-sourcing loadSavedGame snapshot restorer.
Release notes mirror the
[0.3.1]section ofCHANGELOG.md. SeeCHANGELOG.mdfor the canonical source.
Highlights
- Stable dev-environment bootstrapping —
npm run dev:electronno longer fails on a fresh checkout looking forwindow.electronAPI; a concurrentwait-onwatcher holds the Electron launch until thedist-electron/preload.cjsbuild is ready. - Mute/play state persists across fullscreen transitions — hoisting
audioEnabled/isPlayingintoApp.tsxmeans the inline MUTE preference no longer snaps back to "TAP TO PLAY" when the CRT monitor enters or exits fullscreen view. - Watertight interval clearing on snapshot import —
compileIntervalRef+partyVoteIntervalRefroute the compile and party-vote setInterval ids throughuseRef, are guarded withclearIntervalat the top ofloadSavedGame's snapshot-apply path, and the ephemeral compile/party state is explicitly reset to defeat the React-18 auto-batching race where queued setStates from a dying interval's terminal tick could otherwise leak pastclearInterval.
What's New
No new features. The v0.3.0 → v0.3.1 transition is a patch release.
What Changed
No architectural changes or dependency updates.
Bug Fixes
-
npm run dev:electronlaunch failure on fresh checkout — the script was justconcurrently vite + electron ., soelectron .loadedpackage.json'smainfield (dist-electron/main.cjs) which never existed on a fresh checkout. The renderer would load from Vite, but the Electron main process would either fail to start or launch with a missing preload, leavingwindow.electronAPIundefined (src/audio/trackerPlayer.ts::init()would then trip the "requires Electron host" error and disable the music player). Now the script also runsvite build -c electron.vite.config.ts --watchin a third concurrent process, andwait-onblocks the Electron launch until both the Vite dev server and the freshly-builtdist-electron/preload.cjsare ready. The host-watcher keepsmain.cjs/preload.cjsup to date on subsequentelectron/*edits; reload the window (Ctrl+R / Cmd+R) to pick up host changes. -
Inline MUTE resetting on fullscreen toggle —
audioEnabledandisPlayingwere trapped in twouseStatedeclarations insideDemoScreen(one for the inline CRT card, one for the portal-rendered<FullscreenDemoView/>). PressingFto enter fullscreen unmounted+remounted the surface with fresh state, snapping the MusicBadge back to the "TAP TO PLAY" armed state. The two state hooks were hoisted intoApp.tsxascrtAudioEnabled+crtIsPlaying(withuseCallbacktogglerstoggleCrtAudio+toggleCrtPlay) sitting next to the existingcrtMusicTrackstate, and five props (musicTrackStoredName,audioEnabled,isPlaying,onToggleAudio,onTogglePlay) are forwarded through both surfaces. The hero-capture preview mount insrc/preview/CapturePreview.tsxreceives aNOOPcallback stub since the capture pipeline is a stateless preview. As a bonus, this round deduped a stale duplicate of the "Interval ids owned by…" comment block from earlier patch work (the leading copy remains; the orphan was collapsed, leaving exactly 1useRef<>declaration ofcompileIntervalRefandpartyVoteIntervalRef). -
Ghost release / prize drops on saved game load —
triggerAssembleCompilerandstartPartyVotingProcesscapture partial state in theirsetIntervalclosure callbacks; opening LOAD while one of these was in flight left the interval running. The terminal-tick callbacks (finishCompilation(),awardPartyContestPoints()) could fire AFTER the import had reset state, dropping a leftover release or prize credit into the freshly loaded save. Fix:compileIntervalRef+partyVoteIntervalRefuseRef<ReturnType<typeof setInterval> | null>(null)declarations route bothsetIntervalids through the refs; the inner-callbackclearInterval(interval)calls were rewritten to clear+null the ref; a guardedclearIntervalpair sits at the top ofloadSavedGame's try-block to cancel in-flight compile/vote intervals before the snapshot applies; and an explicit ephemeral-state reset block (setIsCompiling(false),setCompilerProgress(0),setCompilerLogs([]),setShowCompilingOverlay(false),setActiveParty(null),setIsPartyRunning(false),setPartyStep(0),setPartyRivals([]),setPartyVoteTally({}),setPartySelectedProdId(''),setPartyContestLogger([])) defeats the React-18 auto-batching race where queued setStates from a dying interval's terminal tick would otherwise leak throughclearInterval.sim/__tests__/loadDuringImport.smoke.tswas rewritten for determinism (amakeInterval()stub + manualtick()/clear()cycle instead of realsetInterval(..., 50)which was flaking under sequentialnpm run test:allevent-loop pressure); all three scenarios (positive-control bug repro, compile-interval cleared, vote-interval cleared) are green.
Upgrade Notes
There is no installer / binary distribution for v0.3.1 — this is a code-only patch.
For end-users: if you are currently running the v0.3.0 installed binary (NSIS installer or portable exe), you are unaffected by the npm run dev:electron build-script fix and unaffected by the music-player state quirk (which only manifests on the inline CRT monitor when toggling between inline and fullscreen modes in vite dev). The loadSavedGame interval-clear fix would be observable if you ever clicked LOAD while a demo was mid-compile or while a party vote was mid-flight in v0.3.0, but v0.3.1 ships this fix and the bug never reached production binaries. Wait for the next feature-drop release (v0.4.0) for a packaged installer with all three fixes bundled.
For contributors: simply git pull / git checkout v0.3.1 / npm install. npm run dev:electron now reliably rebuilds the Main-process preload rather than crashing on a missing window.electronAPI. The new mid-flight interval clear pattern (compileIntervalRef + partyVoteIntervalRef) is the precedent for any future interval-typed setState chain.
Verification
npm run lint(tsc --noEmit) — exit 0npm run test:all— 7/7 smokes green, including the rewritten deterministicloadDuringImportsmoke (Scenarios A/B/C: positive-control bug repro WITHOUT clearInterval, compile interval IS cleared on import, vote interval IS cleared on import)npm run audit:docs— doc/sim parity gate greennpm run build— clean renderer bundle- Electron
worklet://+npm run dev:electronrounds-trip —window.electronAPIpopulated on first launch;reload windowpicks up subsequentelectron/*edits
License: Apache-2.0 (per the SPDX headers and package.json). By submitting a PR, you agree to license your contribution accordingly.