Round 3's review found the enumeration approach (patchStore()'s blocked
list) was still incomplete, and worse, live rather than dormant: keyboard
shortcuts (Delete/Backspace/S) bypass the tour's click-blocking backdrop
entirely, since CSS pointer-events does nothing to a window-level keydown
listener. timeline.js's shortcut handler calls removeSelectedScenes(),
removeAudioTrack(), removeSelectedOverlay(), and splitScene() -- none of
them in the blocked list -- and every one ends in scheduleSave(), whose
setTimeout calls the closure-scoped commit() directly, reaching a real,
unmocked PUT /funpack/api/projects/<id>. tour-s1 is pre-selected from the
moment the tour boots, so pressing Delete at any point immediately
triggers this -- no click needed, no specific step required.
The blocked-list pattern (three rounds of fixes: parsed.scenes crash,
syncFromPreview, saveClipToMediaBin) was closing this one caller at a
time and kept finding another. Fixed at the root instead: every mutating
path in store.js funnels through the single closure-scoped commit()
function before it can reach the network, so that's the one place a
tour-mode guard covers every current AND future caller by construction,
without needing tour.js to know the name of anything that might call it.
Verified live: dispatched a real Delete keydown during the tour with
tour-s1 selected (the default boot state) -- the scene still gets removed
locally (harmless, it's fake demo data), but zero requests to
/api/projects appear in the network log across the full debounce window
after the fix, versus a real PUT before it. Confirmed the guard is a
no-op outside tour mode (window.__FUNPACK_TOUR__ is false there) and the
full 16-step tour sequence still completes with zero thrown errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>