feedBack/tests/js
Byron Gamatos 756588678b
fix(plugins): make a module plugin actually re-evaluate on reload (#879) (#897)
A plugin reload silently did nothing for scriptType:"module" plugins. ES modules are
evaluated ONCE PER URL PER DOCUMENT, so re-inserting a <script type="module"> whose src
the module map has already seen fires `load` without re-running the body — and the loader
then recorded the reload as applied. A no-op that reported success.

THE ISSUE UNDERSTATES IT. #879 says "upgrades are fine — a new version yields a new URL".
That is true of screen.js and FALSE of the plugin. I drove a real browser through
install(1.0.0) -> upgrade(1.1.0) -> rollback(1.0.0), counting evaluations of src/main.js:

    ONE.

Not three, not two. The upgrade re-runs the one-line screen.js shim at its new ?v= URL;
the shim does `import './src/main.js'`; a relative specifier resolves against the base URL
WITH THE QUERY DROPPED; that is the same URL as before; the module map hands back the
already-evaluated v1.0.0 module. The plugin's own code never re-ran. Busting the entry
point cannot fix this, whatever token you hang off it.

So the token goes in the PATH: /api/plugins/<id>/g/<n>/screen.js. From there
'./src/main.js' resolves to /api/plugins/<id>/g/<n>/src/main.js — every relative import
inherits it, at every depth, for free. No import-specifier rewriting (which could never
see `import(expr)` anyway). Same browser drive after the fix: THREE evaluations.

Keyed on the plugin ID, not id@version: EVERY re-load of a module plugin needs a fresh
path, not just a rollback. First load keeps the stable ?v= URL, so the ETag/304 live-edit
caching the R0 rails depend on is untouched. Classic-script plugins are not affected and
never take a /g/ path.

━━━ A PATH REWRITE, NOT TWO MIRRORED ROUTES ━━━

Codex caught this, and it was right. The token shifts the BASE URL, so EVERYTHING the
module graph resolves relatively moves with it — not only imports.
`new URL('../assets/worklet.js', import.meta.url)` from /api/plugins/x/g/1/src/main.js
resolves to /api/plugins/x/g/1/assets/worklet.js. Mirroring only screen.js and src/ would
have fixed imports and 404'd every asset, worklet and wasm file the graph reaches — and
would have broken again the next time someone added a plugin route.

So the /g/<token> segment is STRIPPED BEFORE ROUTING. Every plugin route, present and
future, works under the prefix with no extra wiring. The token is opaque and never joined
into a filesystem path, so containment still rests entirely on the same safe_join.

Codex then caught a [P3] in that: eagerly re-encoding raw_path with latin-1 raises
UnicodeEncodeError on a valid plugin file like src/工具.js, 500ing a request the plain
route serves fine. raw_path is informational and Starlette routes on scope["path"], so the
mutation is simply gone — and leaving raw_path as the client sent it is more truthful for
logs anyway.

TESTS. tests/js/plugin_module_rollback.test.js (5) + 8 in test_plugin_src_route.py:
identical bytes under the prefix, the whole graph one and two levels deep, ASSETS (the
Codex [P2]), every plugin route, non-ASCII filenames (the [P3]), an opaque token, and
containment asserted as PARITY with the un-prefixed route rather than a guessed 404 —
`../screen.js` legitimately 200s on both, because the URL normalises before routing.
All bite-tested: reverting the fix fails the rollback tests, disabling the rewrite fails
the asset tests.

Two harnesses re-anchored on `script.src = _pluginScriptUrl(` — the URL literal they keyed
on now lives in the helper, further down the file, so their slice ran off the end.

node 1045, pytest 2404, ESLint 0, Codex 0.

Closes #879

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:19:59 +02:00
..
alpha_warning_banner.test.js refactor(ui)!: remove the classic v2 shell — v3 is the only UI (R3a) (#871) 2026-07-11 16:33:03 +02:00
app_tour_library.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
audio_effects_host.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_effects_test_harness.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_element_accessor.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
audio_session_compat.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_session_host.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_session_input.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_session_mix.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_session_monitoring.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_session_routes.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_session_stems_bridge.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_session_stems.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
audio_session_test_harness.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
autoplay_exit.test.js refactor(app): carve the player controls out of app.js (R3a) (#891) 2026-07-11 22:12:34 +02:00
badges_handedness.test.js feat(v3): choose handedness in the instrument selector + onboarding (give lefties a break) (#793) 2026-07-05 23:50:30 +02:00
beats_loaded.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
capabilities_claims.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capabilities_diagnostics.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capabilities_lifecycle.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capabilities_manifest_runtime.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capabilities_multi_provider.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capabilities_ownership.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capabilities_test_harness.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capabilities_versioning.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
capability_inspector_nav.test.js refactor(app): carve the plugin loader out of app.js into static/js/ (R3a) (#878) 2026-07-11 18:18:00 +02:00
capability_inspector_render.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
diagnostics_summary.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
drum_keys_highway_3d_resize_reframe.test.js Fix 3D drum/keys highways not resizing on fullscreen under splitscreen (#723) 2026-07-02 18:42:26 +02:00
edit_metadata_modal.test.js fix(library): Edit Metadata modal — editable Year + don't close on drag-release outside (#623) 2026-06-28 13:42:02 +02:00
editor_pending_view.test.js feat(player): seed editor region handoff state (#762) 2026-07-05 23:37:39 +02:00
handshapes_pipeline.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_3d_arp_deferral.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
highway_3d_camera_framing.test.js fix(highway_3d): dolly back so the fret-number row can't clip off the bottom (#633) 2026-06-29 15:31:24 +02:00
highway_3d_context_loss.test.js fix(highway_3d): recover from WebGL context loss instead of crashing on alt-tab (#790) 2026-07-05 22:20:32 +02:00
highway_3d_fret_label_render_order.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
highway_3d_fret_row_labels.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
highway_3d_fret_spacing.test.js fix(highway): apply 3D fret-spacing live instead of reloading (#561) (#562) 2026-06-22 12:08:22 +02:00
highway_3d_lean_sustain.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
highway_3d_lefty.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_3d_overlay_full_cover.test.js fix(highway): stop stale viz frame bleeding through after switching visualizations (#565) 2026-06-22 13:27:21 +02:00
highway_3d_panel_controls.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
highway_3d_pool_warm.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
highway_3d_render_order.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
highway_3d_resize_reframe.test.js perf: remove throttled-trace residuals — program churn, per-frame rect, HUD clock 2026-07-02 00:40:52 +02:00
highway_3d_slide_target.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
highway_3d_smooth_clock_pause.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_3d_sustain_bloom.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
highway_3d_sustain_rail.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
highway_3d_wide_fov.test.js Address review: Reset on All restores defaults verbatim 2026-07-01 02:59:51 -04:00
highway_adaptive_scale.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_audio_barrier.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
highway_bend_curve.test.js fix(bend): GP8 short-bend curve loss + 2D curve timing + 3D bnv gating (#535) 2026-06-21 00:46:36 +02:00
highway_chord_harmony.test.js feat(highway): render caged + guideTones teaching labels (§6.6) (#545) 2026-06-21 11:58:00 +02:00
highway_chord_render_cache.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_colors_facade.test.js refactor(app): carve the highway string-colours out of app.js (R3a) (#883) 2026-07-11 19:22:14 +02:00
highway_filtered_notes.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_monotonic_clock.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_note_state.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_pause_throttle.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_phrases.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_renderer_swap_canvas_reset.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
highway_string_colors.test.js refactor(app): carve the highway string-colours out of app.js (R3a) (#883) 2026-07-11 19:22:14 +02:00
highway_teaching_marks.test.js feat(highway): render teaching marks fg/ch/sd on 2D + 3D (§6.2.2) (#538) 2026-06-21 07:58:04 +02:00
highway_visibility.test.js refactor(highway): lift 79 per-instance closure vars into hwState (R3c H lift) (#849) 2026-07-10 23:19:20 +02:00
host_contract.test.js refactor(app): the host seam + carve section practice out of app.js (R3a) (#887) 2026-07-11 20:58:41 +02:00
juce_engine_reroute.test.js refactor(app): carve the JUCE/desktop audio shims out of app.js (R3a) (#893) 2026-07-11 22:44:03 +02:00
legacy_shim_hits.test.js fix(plugins): make a module plugin actually re-evaluate on reload (#879) (#897) 2026-07-12 00:19:59 +02:00
library_card_actions.test.js Purge external-format terminology from code, tests and docs 2026-06-16 19:36:53 +01:00
live_guitar_tone_source.test.js fix(tests): re-green the JS suite — 18 stale source-shape tests + 1 real seek-reason violation (#740) 2026-07-03 14:35:07 +02:00
live_performance_hud.test.js fix(v3): reset live performance HUD on backward seek / restart (#607) 2026-06-27 00:47:43 +02:00
loop_api.test.js refactor(app): carve the A–B loop out of app.js (R3a) (#888) 2026-07-11 21:32:31 +02:00
loop_restart.test.js refactor(app): carve count-in (and the song-credits overlay) out of app.js (R3a) (#890) 2026-07-11 22:05:22 +02:00
midi_input_domain.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
note_detection_domain.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
pedal_cables.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
play_button_reroute_guard.test.js refactor(app): carve the playback transport out of app.js — and RETIRE 8 host hooks (R3a) (#894) 2026-07-11 23:26:35 +02:00
play_queue_peek.test.js play-queue: peekNext() — expose the following track for queue-aware UIs (#719) 2026-07-02 20:50:56 +02:00
play_queue_shuffle.test.js feat(v3): playlist shuffle toggle 2026-07-03 13:07:47 +02:00
playback_app_adapter.test.js refactor(app): carve the playback transport out of app.js — and RETIRE 8 host hooks (R3a) (#894) 2026-07-11 23:26:35 +02:00
playback_compat.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
playback_diagnostics.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
playback_domain.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
playback_test_harness.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
plugin_hydration_wipe.test.js refactor(app): carve the plugin loader out of app.js into static/js/ (R3a) (#878) 2026-07-11 18:18:00 +02:00
plugin_loader_script_type.test.js fix(plugins): make a module plugin actually re-evaluate on reload (#879) (#897) 2026-07-12 00:19:59 +02:00
plugin_module_rollback.test.js fix(plugins): make a module plugin actually re-evaluate on reload (#879) (#897) 2026-07-12 00:19:59 +02:00
plugin_style_injection.test.js refactor(app): carve the plugin loader out of app.js into static/js/ (R3a) (#878) 2026-07-11 18:18:00 +02:00
plugins_page.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
progress_calibration.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
progression_notifications.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
progression_progress_events.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
renderer_bus_feeder.test.js refactor(app): carve the JUCE/desktop audio shims out of app.js (R3a) (#893) 2026-07-11 22:44:03 +02:00
section_practice_dismiss.test.js refactor(app): the host seam + carve section practice out of app.js (R3a) (#887) 2026-07-11 20:58:41 +02:00
settings_autosave.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
song_close.test.js fix(tests): re-green the JS suite — 18 stale source-shape tests + 1 real seek-reason violation (#740) 2026-07-03 14:35:07 +02:00
song_credits_overlay.test.js refactor(app): carve count-in (and the song-credits overlay) out of app.js (R3a) (#890) 2026-07-11 22:05:22 +02:00
song_event_payload.test.js refactor(app): carve the playback transport out of app.js — and RETIRE 8 host hooks (R3a) (#894) 2026-07-11 23:26:35 +02:00
song_restart.test.js refactor(app): lift the shared player state onto a container (R3a) (#889) 2026-07-11 21:52:51 +02:00
song_seek.test.js refactor(app): carve the playback transport out of app.js — and RETIRE 8 host hooks (R3a) (#894) 2026-07-11 23:26:35 +02:00
speed_reset.test.js refactor(app): carve the player controls out of app.js (R3a) (#891) 2026-07-11 22:12:34 +02:00
test_utils.js Clean release snapshot 2026-06-16 18:47:13 +02:00
tour_engine.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
tuner_auto_open.test.js refactor(app): carve the tuning-display helpers out of app.js (R3a) (#884) 2026-07-11 19:37:44 +02:00
tuner_core.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
tuning_display.test.js refactor(app): carve the tuning-display helpers out of app.js (R3a) (#884) 2026-07-11 19:37:44 +02:00
tuning_targets.test.js refactor(app): carve the tuning-display helpers out of app.js (R3a) (#884) 2026-07-11 19:37:44 +02:00
v3_add_to_playlist_menu.test.js feat(v3): add "Add to playlist" to a song's ⋮ More menu (#625) 2026-06-28 13:58:14 +02:00
v3_az_rail.test.js test(v3): sync A–Z rail assertion to the railParams refactor (#702) (#714) 2026-07-02 14:03:05 +02:00
v3_collections.test.js feat(library): smart collections as a library provider (#641) 2026-06-29 10:42:41 +02:00
v3_favorites_toggle.test.js fix(v3): drawer fav-sync honours data-fav-idle (no dim-heart on List View) (#717) 2026-07-02 14:52:40 +02:00
v3_keep_practicing.test.js fix(tests): re-green the JS suite — 18 stale source-shape tests + 1 real seek-reason violation (#740) 2026-07-03 14:35:07 +02:00
v3_library_refresh.test.js refactor(app): carve the library out of app.js (R3a) (#896) 2026-07-11 23:30:30 +02:00
v3_playlist_cover.test.js feat(v3): content-dependent playlist covers + custom art (#626) 2026-06-28 14:10:15 +02:00
v3_songs_score_badge_refresh.test.js fix(v3): decode stats:recorded filename so post-play score badge refreshes (#620) 2026-06-28 12:39:13 +02:00
v3_songs_scroll.test.js feat(v3): DOM-virtualize the Songs grid (#636 item 3 stage 2) (#643) 2026-06-29 12:26:29 +02:00
v3_songs_tuning.test.js refactor(app): carve the tuning-display helpers out of app.js (R3a) (#884) 2026-07-11 19:37:44 +02:00
v3_songs_window_recycle.test.js fix(v3): recycle library grid cards on scroll instead of rebuilding the window (#742) 2026-07-05 00:19:46 +02:00
v3_theme_read_api.test.js feat(v3): host theme read surface — window.feedBack.theme + always-present --fbv-* tokens (#646) 2026-07-02 13:57:58 +02:00
v3_user_select_policy.test.js fix(v3): reject accidental text-selection of UI chrome (user-select policy) (#637) 2026-06-29 14:44:56 +02:00
venue_instrument_pov.test.js Clean release snapshot 2026-06-16 18:47:13 +02:00
venue_mood_fx.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
venue_scene_3d.test.js refactor(app): carve the viz layer out of app.js — and delete the loader seam (R3a) (#880) 2026-07-11 18:41:52 +02:00
venue_viz.test.js refactor(app): carve the viz layer out of app.js — and delete the loader seam (R3a) (#880) 2026-07-11 18:41:52 +02:00
visualization_domain.test.js rename: slopsmith → feedBack, byron → got-feedBack (#537) 2026-06-23 11:03:01 +02:00
window_contract.test.js refactor(app): make app.js's window contract explicit — 66 names (R3a) (#874) 2026-07-11 17:09:18 +02:00
working_tuning_capability.test.js feat(core): working-tuning lifecycle — launch default, verified decay, idempotent re-injection + tests (working-tuning PR 9a) (#669) 2026-07-01 11:07:58 +02:00
working_tuning.test.js settings: add host instrument profiles (#753) 2026-07-05 00:16:42 +02:00