diff --git a/movie_editor/backend/timeline.py b/movie_editor/backend/timeline.py index 099894d8..a6bf2c7d 100644 --- a/movie_editor/backend/timeline.py +++ b/movie_editor/backend/timeline.py @@ -597,6 +597,10 @@ class Project: # Cut order: scene ids in TIMELINE (result) order, independent of plan (scenes) order. # Empty = follow plan order (back-compat). Reordering the plan never touches this. timeline_order: list = field(default_factory=list) + # True once the user explicitly reordered a timeline clip (◀ ▶). Until then the cut order + # tracks the plan, so editing the global prompt re-derives it cleanly instead of letting a + # stale id sequence scramble the clips against the plan badges. + timeline_manually_ordered: bool = False created_at: float = field(default_factory=time.time) updated_at: float = field(default_factory=time.time) @@ -634,6 +638,7 @@ class Project: scene_renders=dict(d.get("scene_renders") or {}), scene_ghosts=list(d.get("scene_ghosts") or []), timeline_order=list(d.get("timeline_order") or []), + timeline_manually_ordered=bool(d.get("timeline_manually_ordered", False)), created_at=float(d.get("created_at", time.time())), updated_at=float(d.get("updated_at", time.time())), ) diff --git a/movie_editor/frontend/store.js b/movie_editor/frontend/store.js index 3841f2f0..ea6042b9 100644 --- a/movie_editor/frontend/store.js +++ b/movie_editor/frontend/store.js @@ -488,6 +488,9 @@ _applyDetectedTransitions(next, v.transitions, text); state.project.scenes = next; state.sceneGhosts = ghostsOut; + // The global prompt is authoritative on plan order — reconcile the cut order to it now + // (no-op when the user has pinned a manual order; otherwise tracks the plan). + _ensureTimelineOrder(); const prevSel = state.selectedSceneId; const stillSel = prevSel && next.some((s) => s.id === prevSel); const firstId = stillSel ? prevSel : (next[0]?.id || null); @@ -1422,6 +1425,10 @@ function _ensureTimelineOrder() { const p = state.project; if (!p) return; const ids = (p.scenes || []).map((s) => s.id); + // Until the user explicitly pins a custom cut order (◀ ▶), the timeline follows the plan. + // This keeps an edit to the global prompt — which rebuilds the plan in its own order — from + // leaving a stale id sequence behind that scrambles the clips against their plan badges. + if (!p.timeline_manually_ordered) { p.timeline_order = ids; return; } const idSet = new Set(ids); const cur = (Array.isArray(p.timeline_order) ? p.timeline_order : []).filter((id) => idSet.has(id)); const inCur = new Set(cur); @@ -1450,6 +1457,9 @@ if (i === j) return; _historyRecord(); order.splice(i, 1); order.splice(j, 0, id); + // The user has now pinned a custom cut order — a global-prompt edit must preserve it + // rather than re-deriving the order from the plan. + p.timeline_manually_ordered = true; notify(); scheduleSave(); }