From fe8d30ce3e8fda91516ee8afadc4662139c39efb Mon Sep 17 00:00:00 2001 From: Byron Gamatos Date: Mon, 22 Jun 2026 12:08:22 +0200 Subject: [PATCH] fix(highway): apply 3D fret-spacing live instead of reloading (#561) (#562) window.h3dSetFretSpacing was the only 3D-highway setting that applied via location.reload(). The SPA boots with #home as the active screen and has no restore-last-screen mechanism, so the reload ejected the user from Settings onto the home screen. Apply it live like every other 3D-highway setting: rebind the module-scope _h3dFretUniform flag (so panels mounted later this session pick up the new mode), recompute the two fretX-derived scalars baked at init (_fretLabelScaleRefW, FRET_WIDTH_MID), and broadcast a 'fretSpacing' change over the existing _bgEmitChange pub-sub so every mounted panel rebuilds its board via buildBoard(). Per-frame note geometry already reads fretX live. Settings copy updated (no longer reloads) and tests/js pin the no-reload / live-rebuild behavior. Co-authored-by: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + plugins/highway_3d/screen.js | 39 ++++++++++++++++++++++-- plugins/highway_3d/settings.html | 2 +- tests/js/highway_3d_fret_spacing.test.js | 30 ++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d1bb3..be7b98e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **v3 library: exact artist/album filters + scroll/page-depth restore** (slopsmith#857). The v3 Songs toolbar gains Artist and Album dropdowns (Album populates from the selected artist and stays disabled until one is chosen), backed by new exact, case-insensitive (`COLLATE NOCASE`) `artist` / `album` query params threaded through `MetadataDB._build_where` → `query_page` / `query_artists` / `query_stats` and the `/api/library`, `/api/library/artists`, `/api/library/stats` endpoints (the free-text `q` search stays fuzzy and composes with the exact filters). The artist/album catalog is fetched independently of the active artist/album selection so the dropdowns always list the full set for the current provider/search. The toolbar is now sticky so filter controls stay reachable when browsing deep libraries, and returning from the player restores the previous scroll position **and** the loaded infinite-scroll page depth via a `sessionStorage` snapshot keyed by a filter/sort/view state hash (invalidated whenever those change, so a filter change still resets to the top). Tests: `tests/test_library_filters.py` (backend artist/album filters), `tests/js/v3_songs_scroll.test.js` (state-hash + snapshot helpers). ### Fixed +- **Changing Settings → 3D Highway → Fret spacing no longer ejects you to the home screen.** The `highway_3d` plugin's `h3dSetFretSpacing` was the lone 3D-highway setting that called `location.reload()` to apply — and since the SPA boots with `#home` as the active screen (`index.html` `.screen.active`), the reload dropped the user out of Settings onto the homescreen. It now applies live like every other 3D-highway setting: it rebinds the module-scope `_h3dFretUniform` flag (so panels mounted later this session pick up the new mode), recomputes the two `fretX`-derived scalars that were baked at init (`_fretLabelScaleRefW` for fret-label sprite scaling, `FRET_WIDTH_MID` for camera hysteresis), and broadcasts a `fretSpacing` change over the existing `_bgEmitChange` pub-sub so every mounted panel rebuilds its board via `buildBoard()`. Per-frame note geometry already reads `fretX` live and needs no rebuild. No page reload, so the Settings screen stays put. Source-level regression tests in `tests/js/highway_3d_fret_spacing.test.js` now pin the no-reload / live-rebuild behavior. - **v3 library scroll-restore no longer breaks the classic v2 UI or drops off-screen searches** (slopsmith#857). Two regressions in the scroll-restore work above: (1) `playSong` remapped `home`-launched songs to return to the `#v3-songs` screen unconditionally, but `static/app.js` is shared with the v2 UI (served at `/v2` / `SLOPSMITH_UI=v2`) where that screen does not exist — Esc-from-player then called `showScreen('v3-songs')`, which threw on the missing element and stranded the user on a blank screen with playback still running; the remap now applies only when `#v3-songs` is present. (2) The Songs screen-entry fast-path skips reloading to preserve scroll, but the global topbar search routed through it, so once Songs had been visited, searching from another screen navigated there without applying the new query; the screen now tracks the state hash each fetch reflects and refetches when it has drifted, keeping the scroll-preserving no-op only when nothing changed. - **An active custom highway renderer is no longer starved of `draw()` when it hides the canvas** (#819). The per-frame draw gate in `static/highway.js` bailed on `if (!_lastVisible) return`, which conflated two different "hidden" states: a genuine off-screen canvas (`offsetParent === null` — navigate-away / `display:none` splitscreen panel, #246) versus a renderer-set *override-hide* (`setVisible(false)`, where an opaque overlay covers the canvas but the active renderer keeps painting its own surface). The gate now only pauses everything for the off-screen case (and still pauses the default 2D renderer on an override-hide); the **active custom renderer** keeps receiving `draw()` through its own override-hide. The `highway:visibility` event still fires before the gate, so sibling overlay renderers (e.g. 3D Highway's `.h3d-wrap`) still pause. This is the core-side root cause behind the Tab View cursor freezing in single-player (slopsmith#734; worked around plugin-side in slopsmith-plugin-tabview#25). - **Screensaver no longer kicks in during windowed-mode playback** (#686). While a song is playing, `static/app.js` now holds a [Screen Wake Lock](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API) (`navigator.wakeLock.request('screen')`) so the OS display/screensaver stays awake even though only audio + the highway animation are active and the keyboard/mouse are idle. The lock is acquired on `song:play`/`song:resume` and released on `song:pause`/`song:ended`/`song:stop` (kept only while actually playing), and re-acquired on `visibilitychange` when the tab refocuses (the API auto-releases a lock whenever the page is hidden). Both the HTML5 `