From c020ee8ca8e96fa11583245c260f3b6305bb5b3e Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Tue, 15 Sep 2026 22:30:50 +0300 Subject: [PATCH] fix: tour -- Sync scenes from preview could reach a real save, and a demo text mismatch Adversarial review of ee2c0d5 (the parsed.scenes crash fix) found the crash fix's own side effect: stopping the crash meant "Sync scenes from preview" became clickable for the first time during the tour. That button calls syncFromPreview() -> scheduleSave() -> a 5s-debounced closure-scoped commit() -> a real PUT /funpack/api/projects/. patchStore() already blocks "commit", but only as the exported Store.commit property -- scheduleSave()'s internal setTimeout calls the closure-scoped commit function directly, bypassing that patch entirely. This directly contradicts the tour's own welcome copy ("nothing here touches your real files"). Fixed by blocking syncFromPreview itself, the same way every other mutating entry point (generate/save/delete/import/...) is already blocked -- consistent with the existing pattern rather than trying to patch scheduleSave's internals. Also fixed a cosmetic mismatch the review caught: the demo preview's middle scene had prose text while its actual timeline clip (tour-s2, a video clip) has none -- now both agree. Verified live: clicked "Sync scenes from preview" mid-tour, confirmed the block toast fires and no PUT request is made (watched network across the full 5s+ debounce window), then re-ran the full tour sequence to completion with zero thrown errors. Co-Authored-By: Claude Sonnet 5 --- app/legacy/tour.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/legacy/tour.js b/app/legacy/tour.js index 57e9b658..cac30f85 100644 --- a/app/legacy/tour.js +++ b/app/legacy/tour.js @@ -269,9 +269,11 @@ // that panel renders (pre-existing in v4's tour.js too, found while // verifying this port live). parsed: { + // Matches demoProject()'s own three scenes verbatim -- tour-s2 is + // a video clip with no prompt text, same as its timeline entry. scenes: [ { text: "Wide establishing shot of the city at dawn, soft haze." }, - { text: "B-roll alley clip." }, + { text: "" }, { text: "Close-up reaction, emotional beat, shallow depth of field." }, ], transitions: [], @@ -342,7 +344,7 @@ "generate", "generateMontage", "generateSelected", "renderFinal", "exportSelected", "saveSelectedToMediaBin", "commit", "newProject", "loadProject", "deleteProject", "importProject", "downloadProject", "uploadMedia", "deleteMedia", "deleteMediaMany", - "interrupt", "resetStudioSession", + "interrupt", "resetStudioSession", "syncFromPreview", ]; const labels = { generate: "Generate", @@ -359,6 +361,14 @@ downloadProject: "Download project", uploadMedia: "Upload media", resetStudioSession: "Reset Studio session", + // Blocking "commit" only stops external callers of Store.commit -- the + // property gets reassigned below, but scheduleSave()'s own setTimeout + // calls the closure-scoped `commit` function directly, bypassing this + // patch. syncFromPreview() is the one UI entry point that reaches + // scheduleSave() (and, through it, a real API.saveProject network + // call) without going through anything else already on this list, so + // it needs its own block rather than relying on "commit" to cover it. + syncFromPreview: "Sync scenes from preview", }; blocked.forEach((name) => { if (typeof Store[name] !== "function") return;