From 85227cfe399df8d1c9bbfc62823d7d814772af98 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 02:38:25 +0000 Subject: [PATCH 1/6] Install global hooks only once per page; refresh stale docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Host may re-execute screen.js on plugin reload, which re-runs this IIFE. Module state resetting is harmless — it gets rebuilt — but five hooks installed on shared globals are not module state and accumulated: window.playSong got wrapped a second time around the already-wrapped version, so every song load ran the wrapper twice, and the resize, beforeunload and document pointerdown listeners each fired twice per event. Gate all five on a window.__feedBackSplitscreenHooksInstalled flag. It lives on window precisely because it has to outlive the re-execution that resets everything else; a second run leaves the first run's hooks, which are still bound to live closures, in place. Same shape as sectionmap's __slopsmithSectionMapHooksInstalled guard. The two wrappers guard the assignment rather than the definition, so the 83-line playSong body stays at its current indentation and the diff shows the actual change. Declare plugin-runtime-idempotent.v1 in the manifest now that the plugin satisfies it. Add a regression test that loads screen.js twice against one shared window: it counts 2/2/2 listener registrations before this change and 1/1/1 after. Docs: the README clone URL and CLAUDE.md's PR conventions both pointed at a personal fork rather than get-flashbacks. CLAUDE.md also documented the renderer lookup as the pre-rename slopsmithViz_ global; the code has resolved feedBackViz_ first for a while via vizFactory(). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01B4NYejYW2dW7dNtYh7srDy --- CLAUDE.md | 20 +++++++++------ README.md | 2 +- plugin.json | 3 ++- screen.js | 61 ++++++++++++++++++++++++++++++++++++-------- tests/screen.test.js | 51 ++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 21 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 50cbd83..dee122a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,7 @@ screen.js | `wrap` | element\|null | The `#splitscreen-wrap` div, or null when inactive | | `currentFilename` | string\|null | The filename passed to the last `playSong` call | | `arrangements` | array | Arrangement list from the last `song_info` WebSocket message | -| `vizPlugins` | array | `{id, name, …}` entries from `/api/plugins` where `type==='visualization'`. Populated once on page load via `fetchVizPlugins()`. Factory availability (`slopsmithViz_`) is checked lazily in `populateSelect()`, not at fetch time. | +| `vizPlugins` | array | `{id, name, …}` entries from `/api/plugins` where `type==='visualization'`. Populated once on page load via `fetchVizPlugins()`. Factory availability (`feedBackViz_`, legacy `slopsmithViz_`) is checked lazily in `populateSelect()`, not at fetch time. | | `syncInterval` | id\|null | The `setInterval` handle for the time sync loop | | `layoutBtn` | element\|null | The layout `