diff --git a/app/legacy/store.js b/app/legacy/store.js index 615f0e66..810841c5 100644 --- a/app/legacy/store.js +++ b/app/legacy/store.js @@ -636,6 +636,14 @@ } async function applyGlobalPromptQuiet(text) { + // Same shape as commit()'s guard above, and needed for the same reason: + // this has TWO internal closure-scoped callers (setEditorSetting's + // anchorEnabled toggle, applyPromptTemplate) besides its exported + // property, so blocking Store.applyGlobalPromptQuiet from tour.js alone + // would miss both. It reaches a real, unmocked API.parsePrompt network + // call, so it needs its own gate here rather than relying on + // scheduleSaveSilent()/commit() downstream to cover it. + if (window.__FUNPACK_TOUR__) return false; if (!state.project) return false; const trimmed = String(text || "").trim(); if (!trimmed) return false; diff --git a/app/legacy/tour.js b/app/legacy/tour.js index 1e918518..6f1456e0 100644 --- a/app/legacy/tour.js +++ b/app/legacy/tour.js @@ -337,6 +337,20 @@ API.exportClip = ok({}); API.exportClipsCombined = ok({ job_id: "tour-demo" }); API.interrupt = ok({}); + // models.js's "Export settings..." button calls API.settingsCard + // directly -- it never goes through window.Store, so it was never a + // candidate for patchStore()'s blocklist, and it's a real, unmocked + // POST /api/settings-card one click away on the models-modal tour step + // (the FIRST screen that step shows, no sidebar navigation needed). + // The real function resolves a Blob (models.js feeds it straight to + // URL.createObjectURL), so the mock needs to be a real Blob too, not + // JSON -- a 1x1 transparent PNG is enough for the card to render. + API.settingsCard = () => Promise.resolve(new Blob( + [Uint8Array.from(atob( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" + ), (c) => c.charCodeAt(0))], + { type: "image/png" } + )); } function patchStore(Store) {