Skip to content

fix(nav): the library sometimes showed the legacy screen — map 'home' inside showScreen#923

Merged
byrongamatos merged 1 commit into
mainfrom
fix/library-old-home
Jul 12, 2026
Merged

fix(nav): the library sometimes showed the legacy screen — map 'home' inside showScreen#923
byrongamatos merged 1 commit into
mainfrom
fix/library-old-home

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Testers: "randomly, when moving to the library from another menu option, the library shows the old interface — never when a song ends."

What was actually happening

#home is the pre-v3 library screen. The v3 shell replaced it with #v3-songs, and the mapping did exist — but only inside wrappers on window.showScreen, and only for callers that go through window.

Three independent parties monkey-patch it, each capturing whatever happens to be there at the time:

app.js publishes the raw function
  → shell.js wraps it, adding the home → v3-songs mapping
  → the stems plugin wraps it AGAIN (src/main.js:1029), capturing the current value

Plugins load asynchronously. The chain links up in whatever order the race settles — and any capture taken before shell.js installs, or any re-assignment after it, silently drops the mapping.

Hence "randomly."

And the internal callers never touched window.showScreen at all. closeCurrentSong and the Esc-from-settings shortcut call the imported showScreen, which no wrapper ever sees. Reproduced in a browser: the unwrapped function with 'home' lands on the dead legacy screen every time.

"Never when a song ends" is the tell — and it's what identified the mechanism. closeCurrentSong resolves its target through _resolvePlayerOrigin(), which already applies this mapping. That one path was fine, which is exactly why the bug looked random rather than total.

Pre-existing, not a regression from the module carve: the onclick="showScreen('home')" links and the wrapper-only mapping both date to 2026-06-22.

The fix

The guard lives inside showScreen now: one place, in the function every caller routes through, instead of a chain of monkey-patches that must each remember. Wrapper order stops mattering, and the module-internal callers are covered for the first time.

Verified in a browser: the raw, unwrapped showScreen('home') — which reproduced as #home — now lands on #v3-songs, and cannot be undone by any wrapper order.

And a [P1] I introduced, which Codex caught

My first cut mapped both 'home' and 'v3-home', copied straight from _resolvePlayerOrigin.

That's correct there and wrong here. _resolvePlayerOrigin computes where to return to after a song, and landing on the Songs list from the dashboard is right. But #v3-home is the v3 dashboard — a real screen that the shell's Home nav, the onboarding tour, and the dashboard re-render listener all target. Redirecting it would have made Home unreachable.

A legacy alias is not the same thing as a return target.

Only 'home' is mapped now, and a test pins it: re-adding 'v3-home' to the guard fails.

4 tests, bite-tested both ways.

node 1049 · pytest 2425 · ESLint 0 · Codex 0.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Updated legacy Home navigation to route to the Songs screen when available.
    • Preserved the v3 dashboard route and existing behavior when the Songs screen is unavailable.
  • Tests

    • Added coverage for legacy routing, activation order, conditional redirects, and v3 dashboard behavior.

… inside showScreen

Testers: "randomly, when moving to the library from another menu option, the library shows the
old interface — never when a song ends."

━━━ WHAT WAS ACTUALLY HAPPENING ━━━

#home is the PRE-V3 library screen. The v3 shell replaced it with #v3-songs, and the mapping DID
exist — but only inside WRAPPERS on window.showScreen, and only for callers that go through
`window`. THREE independent parties monkey-patch it, each capturing whatever happens to be there
at the time:

    app.js publishes the raw function
      -> shell.js wraps it, adding the home -> v3-songs mapping
      -> the stems plugin wraps it AGAIN (src/main.js:1029), capturing the current value

Plugins load ASYNCHRONOUSLY. The chain links up in whatever order the race settles, and any
capture taken before shell.js installs — or any re-assignment after it — silently drops the
mapping. Hence "randomly".

AND THE INTERNAL CALLERS NEVER TOUCHED window.showScreen AT ALL. closeCurrentSong and the
Esc-from-settings shortcut call the IMPORTED showScreen, which no wrapper ever sees. Reproduced
in a browser: the unwrapped function with 'home' lands on the dead legacy screen EVERY time.

"Never when a song ends" is the tell, and it is what identified the mechanism: closeCurrentSong
resolves its target through _resolvePlayerOrigin(), which ALREADY applies this mapping. That one
path was fine — which is exactly why the bug looked random rather than total.

PRE-EXISTING, not a regression from the module carve: the onclick="showScreen('home')" links and
the wrapper-only mapping both date to 2026-06-22.

━━━ THE FIX ━━━

The guard lives inside showScreen now: ONE place, in the function every caller routes through,
instead of a chain of monkey-patches that must each remember. Wrapper order stops mattering, and
the module-internal callers are covered for the first time.

Verified in a browser: the raw, unwrapped showScreen('home') — which reproduced as #home — now
lands on #v3-songs, and cannot be undone by any wrapper order.

━━━ AND A [P1] I INTRODUCED, WHICH CODEX CAUGHT ━━━

My first cut mapped BOTH 'home' and 'v3-home', copied straight from _resolvePlayerOrigin.

That is correct THERE and wrong HERE. _resolvePlayerOrigin computes where to RETURN TO after a
song, and landing on the Songs list from the dashboard is the right behaviour. But #v3-home is
the v3 DASHBOARD — a real screen that the shell's Home nav, the onboarding tour and the dashboard
re-render listener all target. Redirecting it would have made Home unreachable.

A LEGACY ALIAS IS NOT THE SAME THING AS A RETURN TARGET. Only 'home' is mapped now, and a test
pins that: re-adding 'v3-home' to the guard fails it.

4 tests, bite-tested both ways.

node 1049, pytest 2425, ESLint 0, Codex 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

showScreen now redirects legacy home navigation to v3-songs when available. New tests verify the guard’s implementation, ordering, DOM condition, and preservation of v3-home.

Changes

Legacy home routing

Layer / File(s) Summary
Screen redirect guard
static/js/session.js
showScreen remaps home to v3-songs when the target element exists before applying screen activation logic.
Redirect guard validation
tests/js/show_screen_legacy_home.test.js
Source-based tests verify the redirect, its ordering and DOM guard, and that v3-home is not redirected.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: mapping legacy home to v3 songs inside showScreen.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/library-old-home

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/js/show_screen_legacy_home.test.js (1)

32-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add runtime coverage for the redirect contract.

These assertions inspect source text but never execute showScreen; a broken DOM transition could still pass. Add a focused behavior test for both “#v3-songs present” and “absent” cases, and verify that v3-home remains unchanged. At minimum, make the regex match the entire guard condition rather than separate nearby tokens.

Also applies to: 44-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/js/show_screen_legacy_home.test.js` around lines 32 - 42, Extend the
tests around showScreen with runtime coverage for both present and absent
`#v3-songs` cases, executing the function and asserting the redirect behavior
while confirming v3-home remains unchanged. Replace or supplement the
source-text checks so the regex validates the complete guard condition rather
than matching nearby tokens independently; retain bodyOf only where it remains
necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/js/show_screen_legacy_home.test.js`:
- Around line 32-42: Extend the tests around showScreen with runtime coverage
for both present and absent `#v3-songs` cases, executing the function and
asserting the redirect behavior while confirming v3-home remains unchanged.
Replace or supplement the source-text checks so the regex validates the complete
guard condition rather than matching nearby tokens independently; retain bodyOf
only where it remains necessary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 49359fa6-aa1b-4638-bf72-8388e89acf51

📥 Commits

Reviewing files that changed from the base of the PR and between 545e569 and 6ecb111.

📒 Files selected for processing (2)
  • static/js/session.js
  • tests/js/show_screen_legacy_home.test.js

@byrongamatos
byrongamatos merged commit f27d4f6 into main Jul 12, 2026
5 checks passed
@byrongamatos
byrongamatos deleted the fix/library-old-home branch July 12, 2026 14:05
byrongamatos added a commit that referenced this pull request Jul 12, 2026
…ging, make the shell listen (#924) (#925)

* fix(nav): the library sometimes showed the legacy screen — map 'home' inside showScreen

Testers: "randomly, when moving to the library from another menu option, the library shows the
old interface — never when a song ends."

━━━ WHAT WAS ACTUALLY HAPPENING ━━━

#home is the PRE-V3 library screen. The v3 shell replaced it with #v3-songs, and the mapping DID
exist — but only inside WRAPPERS on window.showScreen, and only for callers that go through
`window`. THREE independent parties monkey-patch it, each capturing whatever happens to be there
at the time:

    app.js publishes the raw function
      -> shell.js wraps it, adding the home -> v3-songs mapping
      -> the stems plugin wraps it AGAIN (src/main.js:1029), capturing the current value

Plugins load ASYNCHRONOUSLY. The chain links up in whatever order the race settles, and any
capture taken before shell.js installs — or any re-assignment after it — silently drops the
mapping. Hence "randomly".

AND THE INTERNAL CALLERS NEVER TOUCHED window.showScreen AT ALL. closeCurrentSong and the
Esc-from-settings shortcut call the IMPORTED showScreen, which no wrapper ever sees. Reproduced
in a browser: the unwrapped function with 'home' lands on the dead legacy screen EVERY time.

"Never when a song ends" is the tell, and it is what identified the mechanism: closeCurrentSong
resolves its target through _resolvePlayerOrigin(), which ALREADY applies this mapping. That one
path was fine — which is exactly why the bug looked random rather than total.

PRE-EXISTING, not a regression from the module carve: the onclick="showScreen('home')" links and
the wrapper-only mapping both date to 2026-06-22.

━━━ THE FIX ━━━

The guard lives inside showScreen now: ONE place, in the function every caller routes through,
instead of a chain of monkey-patches that must each remember. Wrapper order stops mattering, and
the module-internal callers are covered for the first time.

Verified in a browser: the raw, unwrapped showScreen('home') — which reproduced as #home — now
lands on #v3-songs, and cannot be undone by any wrapper order.

━━━ AND A [P1] I INTRODUCED, WHICH CODEX CAUGHT ━━━

My first cut mapped BOTH 'home' and 'v3-home', copied straight from _resolvePlayerOrigin.

That is correct THERE and wrong HERE. _resolvePlayerOrigin computes where to RETURN TO after a
song, and landing on the Songs list from the dashboard is the right behaviour. But #v3-home is
the v3 DASHBOARD — a real screen that the shell's Home nav, the onboarding tour and the dashboard
re-render listener all target. Redirecting it would have made Home unreachable.

A LEGACY ALIAS IS NOT THE SAME THING AS A RETURN TARGET. Only 'home' is mapped now, and a test
pins that: re-adding 'v3-home' to the guard fails it.

4 tests, bite-tested both ways.

node 1049, pytest 2425, ESLint 0, Codex 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(nav): nobody may monkey-patch window.showScreen — add screen:changing, make the shell listen (#924)

window.showScreen was wrapped by THREE independent parties, each capturing whatever happened to be
there at the time:

    app.js publishes the raw function
      -> static/v3/shell.js wrapped it (to call syncActive, and to map home -> v3-songs)
      -> the stems plugin wrapped it AGAIN (to tear down on leaving the player)

Plugins load ASYNCHRONOUSLY, so the chain linked up in whatever order the race settled. A capture
taken before shell.js installed silently dropped the mapping it carried — and the library opened on
the dead legacy #home screen. Testers saw that as "randomly, the library shows the old interface"
(#923).

#923 fixed the symptom by moving the mapping inside showScreen. This removes the CAUSE: neither
wrapper ever needed to be one.

━━━ TWO EVENTS, AND THE DISTINCTION IS THE WHOLE POINT ━━━

    screen:changing  emitted BEFORE anything happens. "I am leaving `from`." Teardown/cancel here.
    screen:changed   emitted after the DOM and data settle. "I am on `id`." Now carries `from`.

screen:changing is new, and it exists because Codex caught me collapsing the two. The stems plugin
tore down its audio graph BEFORE showScreen did anything; screen:changed fires at the very END,
after core awaits library and provider loads — so moving the plugin onto it would have delayed
teardown behind a slow fetch, or skipped it entirely if that fetch threw, and stems would keep
playing on a non-player screen. A test pins the ordering: screen:changing must precede the first
await.

shell.js is a plain screen:changed listener now, like app.js, audio-mixer.js and tour-engine.js
already were. window.showScreen is an unwrapped function again, and tests/js/
no_showscreen_monkeypatch.test.js fails CI if anything in static/ ever assigns to it again — so the
hazard is structurally impossible rather than merely avoided.

━━━ AND A FALLBACK THAT COULD NEVER FIRE ━━━

My retry-if-the-bus-is-late path listened for `slopsmith:capabilities:ready`. Core dispatches
`feedBack:capabilities:ready` (capabilities.js:1536) — the slopsmith: name is the PRE-DMCA event
and nothing has emitted it since the rename. Codex caught it. A guard that cannot fire is worse
than no guard: it reads as protection and is decoration.

(The same dead-event bug turned out to be sitting in THREE of the stems plugin's fallbacks, where
it has silently disabled its lifecycle wiring whenever the bus was late. Fixed in
feedback-plugin-stems#38.)

VERIFIED. A/B against origin/main: the nav highlight and topbar title follow IDENTICALLY with
shell.js as a listener; screen:changing -> screen:changed fire in order with the right {id, from};
window.showScreen is unwrapped; and showScreen('home') still lands on v3-songs.

node 1053, pytest 2425, ESLint 0, Codex 0.

Closes #924

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
byrongamatos added a commit to got-feedBack/feedBack-plugin-stems that referenced this pull request Jul 12, 2026
…ents instead (#38)

This wrapped window.showScreen to tear down the stems graph when leaving the player. It was the
THIRD wrapper on that one global, and it shipped a bug.

Core publishes window.showScreen; the v3 shell wrapped it (carrying the legacy home -> v3-songs
mapping); this wrapped it again. Each captured whatever happened to be there AT THE TIME — and
plugins load ASYNCHRONOUSLY, so the chain linked up in whatever order the race settled. When this
wrapper won, the shell's mapping was silently dropped and THE LIBRARY OPENED ON THE DEAD LEGACY
SCREEN. Testers reported it as "randomly, the library shows the old interface"
(got-feedBack/feedBack#923, #924).

Core already emits screen events for exactly this. Listening costs nothing, cannot clobber
another plugin, and cannot be clobbered by one.

━━━ THREE THINGS CODEX CAUGHT, EACH A SILENT NO-OP ━━━

1. TIMING. My first cut listened to screen:changed — which fires at the END of showScreen, after
   core awaits library and provider loads. The old wrapper ran BEFORE navigation began. Late
   teardown means the stems graph keeps playing on a non-player screen, and a slow or failing
   fetch skips it entirely. Core now emits screen:changing before it does anything, and that is
   the event this wants.

2. BOTH EVENTS, because of cross-repo ordering. screen:changing is NEW — today's released host
   does not emit it. Listening only to the new one would mean teardown() NEVER runs on the current
   host. Listening only to the old one reintroduces the late-teardown problem once the host
   updates. So: both. It is safe by construction — the wrapper this replaces called teardown() on
   EVERY non-player navigation, so it is already idempotent.

3. EITHER BUS. window.slopsmith is the legacy alias of the SAME object (core: `window.slopsmith =
   window.feedBack`), and the rest of this file reads it. Reading only window.feedBack would attach
   nothing on a build that exposes just the old name.

━━━ AND A PRE-EXISTING BUG, FOUND ON THE WAY ━━━

All THREE of this file's "bus is late" fallbacks listened for `slopsmith:capabilities:ready`.
Core dispatches `feedBack:capabilities:ready` (capabilities.js:1536). The slopsmith: name is the
PRE-DMCA event and NOTHING has emitted it since the rename — so every one of those fallbacks has
been DEAD, and wireLifecycleListeners / installCapabilityParticipant silently never wired whenever
the bus was late. All three now listen for the event that is actually dispatched (keeping the old
alias too, harmlessly).

47/47, Codex 0.

Refs got-feedBack/feedBack#924

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant