From 3df4b3545bf12aaf97325e6bdd50767a8dcede6d Mon Sep 17 00:00:00 2001 From: Dejvino Date: Mon, 21 Sep 2026 21:14:02 +0200 Subject: [PATCH] Deep-link view settings survive file drops Dropped files ignored pins while the URL still claimed them, so the census venue replaced the demo's pinned room. Drop loads now inherit venueSel/viz from the query, and stale bank addressing (song/seed/frame/warm) is cleared on drop while view settings stay. --- flow-state/src/main.js | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/flow-state/src/main.js b/flow-state/src/main.js index 2723d6a..8621af4 100644 --- a/flow-state/src/main.js +++ b/flow-state/src/main.js @@ -104,7 +104,20 @@ async function loadFile(file, { seed = null, pins = null, frame = null, warm = n if (state.busy) return; state.busy = true; stopPlayback(); - + // A dropped file carries no pins of its own — inherit the view settings + // from the URL (a demo deep-link's venueSel/viz), so the room and subject + // you came to see survive the new track. The bank path always passes an + // explicit pins object and is unaffected. Frame/warm never carry over: + // they address the old song's timeline. + const fromDrop = pins == null; + if (fromDrop) { + try { + const q = new URLSearchParams(location.search); + if (q.get('venueSel') || q.get('viz')) { + pins = { venueSel: q.get('venueSel'), viz: q.get('viz') }; + } + } catch { /* non-browser — no pins */ } + } if (dom.overlay) { dom.overlay.hidden = true; dom.overlay.style.display = 'none'; @@ -136,8 +149,8 @@ async function loadFile(file, { seed = null, pins = null, frame = null, warm = n // Deep link from a preview (filmstrip figure, venue-gallery card): // pins ride into load() so the arc builds pinned layers once, then // the exact frame with the probe's warm-up so stateful layers land - // converged like the probe render. Dropped files never carry pins — - // only `?song=` loads pass them. + // converged like the probe render. Dropped files carry inherited + // pins (above) but always start at frame 0. const deepLink = !!(pins && (pins.venueSel || pins.viz)) || Number.isFinite(frame); // Prime before the landing seek on deep-link loads: programs link // asynchronously and a draw against an unlinked program is wrong @@ -146,6 +159,7 @@ async function loadFile(file, { seed = null, pins = null, frame = null, warm = n if (deepLink) state.show.prime(); seekTo(Number.isFinite(frame) ? frame : 0, Number.isFinite(warm) ? { warmup: warm } : {}); + if (fromDrop) clearBankParamsFromUrl(); renderPanel(); document.title = `flow-state · ${state.show.fileName}`; console.info('[flow-state]', describeLook(state.show.look)); @@ -174,6 +188,27 @@ if (dom.changeTrack) dom.changeTrack.addEventListener('click', () => dom.fileInp dom.fileInput.addEventListener('change', (e) => { if (e.target.files[0]) loadFile(e.target.files[0]); }); +/** + * A dropped file has no URL identity, so the bank addressing it by name + * (?song=&seed=&frame=&warm=) must go once the file loads — otherwise the + * URL keeps describing the previous song while another plays (and rerolls + * would persist a seed against the wrong name). View settings + * (?venue=&venueSel=&viz=) stay: they describe how you watch, not what. + * Uses replaceState — no reload, no history spam. + */ +function clearBankParamsFromUrl() { + try { + const q = new URLSearchParams(location.search); + if (!q.get('song') && !q.get('seed') && !q.get('frame') && !q.get('warm')) return; + q.delete('song'); + q.delete('seed'); + q.delete('frame'); + q.delete('warm'); + const rest = q.toString(); + history.replaceState(null, '', rest ? `${location.pathname}?${rest}` : location.pathname); + } catch { /* non-browser or sandboxed iframe */ } +} + /** * `?song=centre` — open a bank song straight from a debug page. *