From c967ca81d2754fa036168990898474abaffe94e7 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:50:58 +0300 Subject: [PATCH] fix: Reactive focus says why a view step was not offered --- movie_editor/frontend/reactive_focus.js | 23 ++++++++++++++++++----- shot_camera.py | 3 ++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/movie_editor/frontend/reactive_focus.js b/movie_editor/frontend/reactive_focus.js index 7ce1f20b..6dbf2284 100644 --- a/movie_editor/frontend/reactive_focus.js +++ b/movie_editor/frontend/reactive_focus.js @@ -160,6 +160,17 @@ }); } + // Why no shot was offered a view: the honest reason per shot. + function whyNone(scenes, saved) { + const all = scenes.flatMap((sc) => sc.views || []); + if (!all.length) return "a view only opens shots 2 and later, so the prompt needs two or more [Shot N] blocks."; + return all.map((s) => { + if (s.already) return `shot ${s.shot} already states “${s.stated}”`; + if (saved[s.key]) return `shot ${s.shot} is already decided (forget it in Settings ▸ Refinement & Taste)`; + return `no view can show shot ${s.shot}`; + }).join("; ") + "."; + } + // Called by Store before it queues a run. -> false to abort the run. async function review(project, sceneIds) { const { rf } = window.StudioSettings.read(project); @@ -187,15 +198,17 @@ const todo = pending(scenes, savedViews, "views", (s) => !s.already && s.candidates.length); if (todo.length) steps.push({ kind: "views", title: "Views", page: viewsPage(todo) }); } + // A wanted step with nothing to ask says why, or "no views" looks like "broken". + const whys = []; + if (wantViews && !steps.some((x) => x.kind === "views")) whys.push("Views: " + whyNone(scenes, savedViews)); if (!steps.length) { - // Say why nothing was asked, or "no modal" is indistinguishable from "broken". const why = !seen.with_shot_marks - ? `no [Shot N] found in the ${seen.texts == null ? "" : seen.texts + " "}scene text(s) sent to generation` - : scenes.length ? "every shot is already decided or already has a camera move/view" - : "no shot has a target or view to offer"; - window.Store.get().notice = `Reactive focus: nothing to ask — ${why}.`; + ? `no [Shot N] found in the ${seen.texts == null ? "" : seen.texts + " "}scene text(s) sent to generation.` + : whys.join(" ") || "every shot is already decided or already has a camera move."; + window.Store.get().notice = `Reactive focus: nothing to ask — ${why}`; return true; } + if (whys.length) steps[steps.length - 1].page.nodes.push(el("div", "ov-label", whys.join(" "))); const res = await ask(steps); if (!res) return false; const patch = {}, learn = { decisions: [], views: [] }; diff --git a/shot_camera.py b/shot_camera.py index 866662b0..6e4c81d4 100644 --- a/shot_camera.py +++ b/shot_camera.py @@ -613,8 +613,9 @@ def view_options(text, stats=None): traits = view_traits(picture) cands = sorted(((v, _view_weight(v, traits, stats)) for v in allowed_views(traits)), key=lambda c: -c[1]) + stated = VIEW_STATED.search(CUT.sub("", picture)) out.append({"shot": int(m.group(1)), "key": shot_key(picture), "traits": traits, - "already": bool(VIEW_STATED.search(CUT.sub("", picture))), + "already": bool(stated), "stated": stated.group(0) if stated else "", "candidates": [{"view": v, "score": round(w, 2)} for v, w in cands], "auto": cands[0][0] if cands else None}) return out