From 611955732f4ce4696ffe09e241e6edad76cf64ca Mon Sep 17 00:00:00 2001 From: Carolina <79524656+carochacs@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:15:07 -0600 Subject: [PATCH 1/6] fix: hide mode-inappropriate buttons, dedupe lyrics overlay, rebind detector across highway swaps (#5) - Hide Lyrics/Detect/Channel toggle buttons while a panel is in Lyrics-pane or Jumping-Tab-pane mode, and restore them on exit (matches CLAUDE.md's documented button-hiding contract). - Destroy the per-panel lyrics overlay when entering Lyrics/JT mode (it was rendering on top of the full lyrics pane / JT pane) and restore it on exit if it was on. - Rebuild the per-panel note detector in recreatePanelHighway() so it stays bound to the live highway instance across arrangement switches and viz mode transitions, instead of being orphaned on a stopped one. - Guard popOutPanel() with the _starting in-flight check (same pattern as rebuildLayout()/_redockPanel()) so a pop-out can't race a still-building startSplitScreen(). - Null-check #audio in the tab-view getCurrentTime callback. Addresses items 2-4 and 7 of the shared-architecture bug audit filed in carochacs/feedBack-plugin-splitscreen#4 (item 1, invert persistence, was already fixed in this repo). Co-authored-by: Claude --- screen.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/screen.js b/screen.js index a71067f..07d8713 100644 --- a/screen.js +++ b/screen.js @@ -1394,6 +1394,15 @@ try { hw.setMastery(mastery); hw.resize(); panel.hw = hw; + + // toggleDetect() captures `highway: panel.hw` by value when the detector + // is created — leaving it bound to the just-discarded `old` highway + // here would orphan it on a stopped instance that never tracks the + // new chart. Rebuild it against the fresh `hw` if it was live. + if (panel.detector) { + toggleDetect(panel); + toggleDetect(panel); + } } // ── Per-panel viz controls ("3D ⚙" popover) ── @@ -1608,14 +1617,28 @@ try { panel.hw.stop(); panel.canvas.style.display = 'none'; - // Hide highway-specific buttons and mastery slider + // Hide highway-specific buttons and mastery slider. Lyrics/Detect/Channel + // are meaningless once the highway is stopped and the canvas is hidden — + // leaving lyricsBtn live lets the per-panel lyrics overlay spawn on top + // of the full lyrics pane, duplicating the text. panel.invertBtn.style.display = 'none'; panel.leftyBtn.style.display = 'none'; panel.tabBtn.style.display = 'none'; + panel.lyricsBtn.style.display = 'none'; + if (panel.detectBtn) panel.detectBtn.style.display = 'none'; + if (panel.channelBtn) panel.channelBtn.style.display = 'none'; panel.masteryHeading.style.display = 'none'; panel.masterySlider.style.display = 'none'; panel.masteryLabel.style.display = 'none'; _hideVizControls(panel); + // Destroy the per-panel lyrics overlay so it doesn't stack visibly on + // top of the full lyrics pane, duplicating the text. lyricsOverlayOn + // is left untouched so the overlay comes back on exit if it was on. + if (panel.lyricsOverlay) { + panel.lyricsOverlay.destroy(); + panel.lyricsOverlay.el.remove(); + panel.lyricsOverlay = null; + } panel.lyricsPane = createLyricsPane(panel.panelDiv); panel.lyricsPane.el.style.bottom = (panel.bar.offsetHeight || 28) + 'px'; @@ -1639,6 +1662,9 @@ try { panel.invertBtn.style.display = ''; panel.leftyBtn.style.display = ''; panel.tabBtn.style.display = ''; + panel.lyricsBtn.style.display = ''; + if (panel.detectBtn) panel.detectBtn.style.display = ''; + if (panel.channelBtn) panel.channelBtn.style.display = ''; panel.masteryHeading.style.display = ''; panel.masterySlider.style.display = ''; panel.masteryLabel.style.display = ''; @@ -1650,6 +1676,13 @@ try { panel.arrName.textContent = arrangements[arrIndex]?.name || ''; hookPanelReady(panel); panel.hw.connect(getWsUrl(currentFilename, arrIndex), { onSongInfo: () => {} }); + // Restore the per-panel lyrics overlay if it was on before entering + // lyrics mode. + if (panel.lyricsOverlayOn) { + panel.lyricsOverlay = createLyricsPane(panel.panelDiv, { overlay: true }); + panel.lyricsOverlay.connect(currentFilename, 0); + if (typeof panel.hw.setLyricsVisible === 'function') panel.hw.setLyricsVisible(true); + } savePanelPrefs(); } @@ -1665,10 +1698,18 @@ try { panel.invertBtn.style.display = 'none'; panel.leftyBtn.style.display = 'none'; panel.tabBtn.style.display = 'none'; + panel.lyricsBtn.style.display = 'none'; + if (panel.detectBtn) panel.detectBtn.style.display = 'none'; + if (panel.channelBtn) panel.channelBtn.style.display = 'none'; panel.masteryHeading.style.display = 'none'; panel.masterySlider.style.display = 'none'; panel.masteryLabel.style.display = 'none'; _hideVizControls(panel); + if (panel.lyricsOverlay) { + panel.lyricsOverlay.destroy(); + panel.lyricsOverlay.el.remove(); + panel.lyricsOverlay = null; + } const jtContainer = document.createElement('div'); jtContainer.style.cssText = @@ -1707,6 +1748,9 @@ try { panel.invertBtn.style.display = ''; panel.leftyBtn.style.display = ''; panel.tabBtn.style.display = ''; + panel.lyricsBtn.style.display = ''; + if (panel.detectBtn) panel.detectBtn.style.display = ''; + if (panel.channelBtn) panel.channelBtn.style.display = ''; panel.masteryHeading.style.display = ''; panel.masterySlider.style.display = ''; panel.masteryLabel.style.display = ''; @@ -1718,6 +1762,11 @@ try { panel.arrName.textContent = arrangements[arrIndex]?.name || ''; hookPanelReady(panel); panel.hw.connect(getWsUrl(currentFilename, arrIndex), { onSongInfo: () => {} }); + if (panel.lyricsOverlayOn) { + panel.lyricsOverlay = createLyricsPane(panel.panelDiv, { overlay: true }); + panel.lyricsOverlay.connect(currentFilename, 0); + if (typeof panel.hw.setLyricsVisible === 'function') panel.hw.setLyricsVisible(true); + } savePanelPrefs(); } @@ -2163,7 +2212,7 @@ try { const tv = window.createTabView({ container: tabContainer, getBeats: () => panel.hw.getBeats(), - getCurrentTime: () => document.getElementById('audio').currentTime, + getCurrentTime: () => { const a = document.getElementById('audio'); return a ? a.currentTime : 0; }, }); await tv.load(data); tv.startSync(); @@ -2588,6 +2637,15 @@ try { // (slot collapses; rebuildLayout reflows remaining panels). function popOutPanel(panel) { if (!currentFilename) return; + // A start is in flight (e.g. this is the very first pop-out of the + // session and startSplitScreen is still awaiting _vizPluginsReady). + // teardownPanels() below would rip out that half-built layout out + // from under it — bail instead of racing it, same hazard rebuildLayout() + // and _redockPanel() already guard against via `_starting`. + if (_starting) { + _showMainToast('Split screen is still starting — try popping out again in a moment.'); + return; + } const idx = panels.indexOf(panel); if (idx === -1) return; if (typeof BroadcastChannel !== 'function') { From 94e75f83c7fa8dc78cb685694a6873bc74cb0830 Mon Sep 17 00:00:00 2001 From: Carolina <79524656+carochacs@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:41:13 -0600 Subject: [PATCH 2/6] docs: finish the Slopsmith -> feedBack rebrand in user-facing text and docs (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README: title, intro line, install path/clone URL (was pointing at the stale topkoa/slopsmith-plugin-splitscreen fork instead of this repo), "runs on any feedBack core", and the Requirements section. - CLAUDE.md: "loaded by feedBack core", and the Git/PR conventions section (was entirely stale, pointing at topkoa/slopsmith-plugin-splitscreen and an "upstream" that isn't this repo's actual setup). - screen.js: the follower-orphaned overlay's user-visible fallback title ("Main Slopsmith window closed" -> "Main feedBack window closed"). Left untouched: window.slopsmithSplitscreen (back-compat alias consumers still read), the slopsmithViz_ factory naming convention, and BroadcastChannel('slopsmith-ss') — these are real external integration contracts other plugins depend on, not branding. Also left sibling-plugin repo links (Stems, Jumping Tab) and slopsmith#NNN issue references as-is since I can't verify whether those external repos/trackers were renamed. Co-authored-by: Claude --- CLAUDE.md | 10 +++++----- README.md | 12 ++++++------ screen.js | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 877c7eb..50cbd83 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Split Screen Plugin — AI Agent Guide -All logic lives in a single IIFE in `screen.js`. There is no build step, no bundler, no imports. The plugin is loaded as a plain `