Root cause the user pushed on: the editor flattened all scenes into ONE prompt
string and Studio re-derived boundaries by parsing text, so a carry scene with no
transition needed an injected 'scene N' delimiter. The editor already KNOWS every
boundary (each generative unit is a scene) — it never needed to smuggle markers
into the prompt.
- timeline.build_combined_prompt: no longer injects anything (verbatim anchor +
scene texts). New build_generation_scene_segments(project) lists the boundaries
the editor already has (each unit's raw text, prefixed with its REAL transition
so effects survive; carry = plain text).
- conditioning.split_scenes_from_segments: canonical split from an explicit scene
list (reuses split_scenes per item for expansion/keys/effect, folds the anchor),
same output shape as split_scenes. refine_v2 gains scene_segments and uses it
when present (count-preserving: a text-less i2v scene keeps its slot, no anchor
shift); falls back to text-splitting only for raw node-pack use.
- server/builder: editor sends scene_segments -> studio_settings.refiner.scenes.
Net: the generation prompt Studio encodes is now exactly what the user typed —
no 'scene N' anywhere, in the prompt or the conditioning. Tests updated to assert
the verbatim prompt + structural segments; new tests for split_scenes_from_segments
(boundaries, empty-scene preservation, anchor/key fold). Fallback path unchanged
(stash-verified zero regression). NOTE: montage generation needs GPU verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Multi-scene bug: the Movie Editor injects a generic 'scene N' separator before
any scene that lacks a leading transition trigger (build_combined_prompt), purely
to force a boundary. split_scenes was splitting on those markers with placement
'start', which KEEPS the marker word in the new scene's text -> 'scene 1',
'scene 2', ... got encoded into every scene's conditioning. With a text-less
leading i2v scene this also produced a label-only first scene (renders as the
anchor with no prompt) and shifted each scene's content down by one.
Fix in the one canonical splitter: a generic 'scene <N>' label (not a real
custom trigger in the transition DB) now cuts SILENTLY -> boundary only, dropped
from both expanded (generation) and raw (editing) text. Real user/DB triggers
keep their configured placement, so 'cut'/'qcut' etc. are unaffected.
Regression test asserts the markers split but never appear in any scene's
expanded/raw text, and that a real trigger still leads its scene.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Chain Sampler's Guidance section already exposes embed_guidance via the
sampler-knob path; add its score-space sibling the same way. Two new knobs in
SAMPLER_KNOBS (score_slider bool, score_slider_strength eta 0-3, dependsOn the
toggle) listed in the chain_guid section beside embed_guidance. Flows through the
existing sampler_inputs path (setSamplerInput -> _run_sampler_inputs -> builder ->
Chain Sampler node); refinement_key_input is already wired, so no backend change.
BachVid + KV-Lock need no UI (automatic, ride the refinement key).
Test: score_slider sampler_inputs pass through _run_sampler_inputs untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Training-free concept sliders (arxiv 2511.00103) in noise-prediction space,
sourced from the learned taste (user's choice). New score_slider + strength(eta)
inputs on the Scene Chain sampler, additive/non-disruptive, reusing the same
learned liked direction + embed_guidance_source + refinement_key_input.
Mechanism: a per-scene model_function_wrapper synthesizes a +/- concept pair from
the base conditioning and the liked direction (no concept prompts), runs 3 forward
passes on quality-phase steps and combines eps_mod = eps_base + eta*(eps_+ - eps_-).
High-sigma steps stay single-pass = the paper's k-step base-only warmup, keeping the
2x cost off early steps. Video-only: +/- conds are audio-protected and the eps blend
is masked to the packed video stream. Composes with embed_guidance (its nudge cancels
in the +/- difference). Installed/torn down per scene in reverse order; threaded
through batch-training re-dispatch. Fully guarded -> falls back to base prediction.
Tests: tests/test_score_slider.py (5) — combine, warmup, eta=0 no-op, eta linearity.
Pre-existing 16 suite failures unchanged (stale-mock/splitter set); zero new.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inversion-free adaptation of 'When to Lock Attention' (arxiv 2603.09657).
Track x0 prediction instability per step (relative step-change of the predicted
clean latent, video-masked = a cheap proxy for the paper's variance-of-x0
hallucination metric over a sliding window). High instability => the model is
still drifting => raise the shared kvlock_scale so BachVid holds the blessed
identity K/V harder; as x0 settles the multiplier relaxes toward base strength.
No DDIM inversion (LTXAV inverts poorly) — locks against the blessed K/V bank.
No-op unless a BachVid bank installed the shared scale list, so the two compose
cleanly. Wired into all three FunPack sampler loops after each model() call;
fully guarded, cannot break sampling. Tests cover capture/inject/scale + the
schedule's stable-vs-jumpy behavior and the no-bank no-op path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Training-free character carry across scenes/sessions (arxiv 2510.21696),
built on the existing LTXAV attn-hook infra. Capture raw K/V at IDENTITY_BLOCKS
during the mid-sigma window into a per-refinement-key temp bank; promote to a
blessed bank on liked ratings (EMA-merge, alongside bless_attn_weights). On
later runs with a blessed bank, lerp the running gen's K/V toward the blessed
identity at matched token positions (packed-/RoPE-safe analogue of the paper's
concat-attend). Wholesale inject at identity blocks (no spatial mask yet).
Exposes a shared kvlock_scale multiplier in transformer_options for the Phase 2
KV-Lock variance scheduler to drive injection strength per step (1.0 = off now).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route the conditioning variant from per-config outcome history, via the one
charter-safe surface: the INTERNAL value-function ascend+search (not user knobs,
so no WYSIWYG / hidden-settings violation). Sampler-owned visual ops stay on the
sampler (boundary law kept).
Directly serves the original complaint (Perfect not locking, Awful reproducing):
- _v2_path_conditioning_plan: matching-arm Beta posteriors -> liked-rate mean ->
"lock" (>=0.6: hold the liked conditioning, skip the stochastic search),
"explore" (<=0.4: widen the search to flee a disliked region), else "normal"
- value_function.search gains eps_scale to widen/tighten the search radius
- threaded conditioning_plan through ascend -> apply_scene_refinement_keys ->
finalize; computed once per run from path_outcomes + prompt concepts; logged
Only active once the per-key VF is ready, since it rides the existing ascend path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Upgrade the seed explore/exploit gate from a flat reuse chance to a proper
bandit over the path-outcome arms:
- _v2_path_arm_beta: Beta posterior over an arm's liked-rate (liked>=0.3 success,
<0.0 failure, neutral ignored; Laplace prior)
- _v2_path_explore_decision: Thompson-sample theta per concept-matching arm vs a
fresh Beta(1,1) explore arm; good arms exploit, dislike-dominated arms route to
a fresh seed ("do something else")
- wired into _v2_choose_successful_seed (falls back to the flat gate with no data)
- shared _v2_path_matching_arms helper; _V2_KEY_REWARD + liked/disliked consts
5 tests incl. a statistical one: a strongly-liked arm rarely explores, a hated
arm almost always does.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make path_outcomes ACT via the one Studio->sampler lever wired today: the seed
(FunPackStudio.run -> _v2_choose_successful_seed -> seed output -> sampler). No
new data paths invented.
The existing seed chooser only exploited liked seeds with zero avoidance — the
headline gap. Now:
- recorder tags each seed sample avoid = reward<0 and not skip_value_function
(Wrong-* excluded: the seed was fine, the words/identity were off)
- _v2_path_disliked_seeds: seeds that gave a low-quality result for matching concepts
- _v2_choose_successful_seed redraws a disliked fallback, drops liked-then-later-
disliked seeds from reuse, and reports how many it avoided
Broader config levers (guidance/lora) need new wiring and are deferred pending
graph-topology confirmation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audited every learning sink in the V2 rating flow. Rating-learning and the
Absolute store already honored skip_learning, but two feedback sinks ran gated
only by feedback_prompt — so a 'Just ignore it' run with typed feedback still
wrote advisor_feedback_history + intent_expansion_memory.
Add _v2_rating_is_discard (forget rating or auto-discarded refusal) and gate
both feedback sinks on it, without touching the active continue/fresh_prompt
modes that legitimately keep feedback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reframe ratings from local steering into global routing: every rating —
not just successful ones — is recorded as a labeled outcome on the per-scene
config "arm" that produced it, so a later planner can route away from arms
that keep generating disliked results.
- _v2_path_signature: arm = family + concepts + guidance config, seed EXCLUDED
(seed is the random draw within an arm); md5 arm_id collapses identical configs
- _v2_update_path_outcomes: learns from EVERY rating, decaying counts so recent
behavior dominates; records seed-level samples for later seed routing
- hooked at the shared per-scene learning hub + single-run path; stored in
per-key state under "path_outcomes"
Pure instrumentation — no generation behavior change yet (Phase 2 = Studio planner).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drives the editor's built-in tour (?mode=tour, fully mocked — no backend/models/
GPU) with Playwright: serves the static frontend, walks window.TourGuide, and
screenshots each step (annotated with its spotlight + caption) while recording a
walkthrough video. Emits NN-*.png stills, an index.md gallery, and
walkthrough.webm/.mp4. Self-maintaining: new tour steps appear automatically.
No more screenshotting each button by hand. node_modules/ and out/ are gitignored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The fake routes object installed into sys.modules as `server` only defined
get/post/delete; server.py also registers put (models) and patch (media), so
importing the real server.py during collection (e.g. test_server.py) raised
AttributeError and aborted the whole run. Add put/patch to the mock.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings the OpenCut Cutting Room rebuild, Anchor-as-guide, Mixed self-image
guide, autocomplete, and the Scene Builder / character / NormalizingSampler
removals onto main. Tree matches dev exactly (discards the obsolete
cutting_edge README doc link).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cutting_edge branch is gone; generalize the section to cover short-lived
non-main branches without the 'cutting edge' branding, same warning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mixed scenes never rendered an inspector image picker — renderSourceField only
called renderImageSource for eff === "image", even though renderImageSource has a
dedicated mixed branch ("Anchor image (mixed i2v)"). So a Mixed anchor could only
be set by dragging onto the timeline clip, unlike image/anchor_guide. Render the
picker for eff === "mixed" too, giving Mixed the same on-timeline + inspector image
visibility as the other anchor modes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two bugs from the Tab update:
1. Tab didn't finish the trigger at end-of-text. The skip-space regex included `$`,
so when the caret was at the end of the field (the common case) NO trailing space
was added — the menu then re-opened on the just-accepted trigger (it matches
itself) and appeared stuck. Drop `$`: always add the space unless a space/delimiter
already follows.
2. Orphaned menu stranded in the top-left with no way to dismiss it. The menu lives in
document.body; when a panel re-render removes its host textarea, removal doesn't fire
blur, so the menu was never cleaned up and a later scroll repositioned it against a
detached element (rect 0,0 → corner). Now: open() sweeps stray .ac-menu (single menu
app-wide) and bails if the textarea is detached; refresh()/position() close on a
detached textarea; and a MutationObserver closes + disconnects when the textarea
leaves the DOM, so it hides on its own.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first cut withheld the anchor_guide image (set_primary=False) to get an
empty latent "for free". That broke real (custom) pipelines: the image vanished
from the timeline and never reached the graph, so a downstream Image Transform
node — which derives width/height from the input image even when i2v is
bypassed — had no input and generation failed.
Corrected model: the image flows into the pipeline like any anchor (copied as
primary -> source_image / image_processing), shows on the timeline, and is
required. The EMPTY LATENT comes from bypassing the i2v node (the value-only
node_overrides), not from withholding the image. On the built-in pipeline there
is no bypassable i2v node, so it runs as a normal i2v anchor + frame-0 guide;
the inspector warns when no i2v node is declared.
- timeline: anchorMediaRef / SRC_ICON / appendSrcBadge show the anchor_guide
thumbnail + a "guide" badge.
- store: _anchorAvailable / _continuesChainRun treat it as an anchor; drop keeps
guide mode instead of forcing plain image.
- backend: _prepare_media / scene_anchor_media_refs / _anchor_media_ref /
source_needs_anchor_media include anchor_guide; removed the now-dead
anchor_guide_media_refs guide-only copy path (image is a normal anchor now).
- inspector: relabel to "Anchor image (guide mode)", warn when no i2v node set.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Filter nodes…" box only showed for "Any node" mode; role-based modes
filled the dropdown directly with no way to search. Now the filter is shown
for any selected model type and applies to role candidates and Any-node alike
(applyFilter drives both paths).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mixed scenes carried prior-scene guides but the FIRST scene (nothing prior to
carry) got no guide attention at all — only the i2v anchor. Generalize the
anchor_guide self-image guide path to also cover mixed: each mixed scene's own
image is added as a reinforcing frame-0 guide (strength 0.35), merged on top of
the carried prior guides. Renamed build_anchor_guide_guides ->
build_self_image_guides. Mixed images are already copied as anchors, so guide
refs resolve via funpack_scene_media_refs with no extra media prep.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New scene Source mode: the image is applied as a frame-0 guide via the
existing guide-attention path while the latent fed to the sampler stays empty
(text-to-video). The image is never the i2v anchor, so frame 0 is not locked.
Backend (timeline.py / server.py):
- SceneSource.guide_strength (0..1, per-scene; default 0.35).
- build_anchor_guide_guides() emits a frame-0 self-image guide per anchor_guide
scene; merge_scene_guide_payloads() overlays it onto continuity/manual stacks.
- anchor_guide images are copied guide-only (set_primary=False) so source_image
is never wired -> empty latent comes free in the built-in pipeline.
- _apply_node_overrides(): force widget inputs on built-graph nodes from a
client-supplied node_overrides list (the optional custom-i2v bypass).
Frontend:
- inspector: Source option + guide image picker + strength slider.
- editor_settings: per-browser "Anchor as guide" section — declare a custom
i2v node and the input/state to force on those runs (value-only). Built-in
pipeline needs none of this.
- store: sends node_overrides only for runs containing an anchor_guide scene;
pipeline_caps: anchor_guide is chain-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Minimize now collapses a FloatingWindow to its title bar where it sits (a
window-shade), instead of sending a pill to a bottom-left dock. The button
toggles +/—; double-click the bar also expands. Removed the dock/pill code
and CSS.
Autocomplete accept (Tab/Enter/click) now appends a trailing space so the
trigger is "finished" and the menu doesn't re-open on the just-completed
trigger (it matched itself); skips the space when a space/delimiter follows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
multiline_textbbox() returns float coords on Pillow >= 10, so tw/th flowed into
Image.new() as floats -> "TypeError: 'float' object cannot be interpreted as an
integer" the first time a styled text overlay was exported. Round the bbox coords
to int before using them for sizes and offsets. Surfaced by running the (previously
PIL-skipped) test_render_text_overlay_styled in the ComfyUI venv; all overlay tests
now pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The canonical generation/preview/key splitter (split_scenes) only treated a
shortcut as a scene cut when the shortcut's TRIGGER word was a key in the
transition DB. But split markers are commonly keyed by the EXPANSION phrase: a
"cut" shortcut expands to "Scene cut.", and the split marker is "scene cut.".
So the lookup `custom_map.get(_norm(raw))` missed, the shortcut was treated as
plain content, and NO cut was made — split_scenes returned one scene for
"A cut B cut C" while the verbatim editor split returned three. That disagreement
collapsed the timeline's middle scenes (and would have generated one scene).
Fix: when the trigger word isn't in the DB, also match the shortcut's EXPANSION
(`custom_map.get(_norm(exp))`). The generic "scene <N>" label is still NOT matched
on expansions, so an incidental "scene 2" inside a content shortcut never cuts.
Verified against the user's on-disk cut/qcut defs: split_scenes now agrees with
the verbatim split (2 scenes for cut x2 / qcut x2 / mixed). Added regression tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The query was the whole token since the last , ; \n delimiter, and a trigger only
matched if that entire token was a substring of it. Typing a normal multi-word
prompt grew the token past any short trigger, so suggestions never appeared.
Now match TRAILING WORD-RUNS of the token, longest first ("golden ho" before
"ho"), keeping substring matching so "@golden" still matches "golden". The longest
run that hits any trigger wins; its span is what acceptance replaces, so completing
a multi-word trigger can't duplicate words. Verified the matching across start-of-
field, mid-prose partials, post-comma, and no-match prose cases.
Also lifted .ac-menu z-index (9000 -> 99999) so it can never render behind the
Composer floating window when completing in the Compose tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reordering was Edit-menu-only (Move Clip Left/Right). Added ◀ ▶ buttons to each
clip's head bar (next to Remove) that call the same moveTimelineClip, disabled at
the ends. Works for both generative and video clips. (Add was already on the
timeline toolbar via + Add → Clip.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The rebuilt editor never exposed character creation/assignment, so the feature
was vestigial: dead frontend plumbing plus generation branches that only fired
for legacy/imported projects. Removed it wholesale per decision.
Backend:
- Deleted movie_editor/backend/characters.py and the /library/characters routes.
- timeline.py: dropped normalize_character_bible / build_characters_anchor /
_load_character_map / _scene_character_prefix / character_media_refs_for_project
and friends. effective_anchor/effective_negative_prompt now return the project's
manual text; resolve_scene_identity_pin(cs) returns the project-level identity
pin only. Removed Scene.character_ids and Project.character_bible fields and the
character_ids fingerprint key.
- Continuity auto-guides keep working off continuity_settings.identity_pin_ref.
Frontend:
- store.js: removed state.characters, the character preview-key plumbing,
load/save/deleteCharacter, sceneCharacterIds/toggleSceneCharacter, character_ids
in patch/archive paths and exports.
- timeline.js: dropped clip-char badge rendering; api.js: dropped character routes;
view_bus.js: dropped chars fingerprint; tour.js: dropped demo characters + copy;
styles.css: dropped .clip-chars/.clip-char.
Tests: removed test_characters.py and the character-bible timeline tests; added
manual-anchor / manual-negative / project-identity-pin tests. 133 passed, 4 skipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both class families had zero JS references in the rebuilt UI. Removed all of
.char-* (character inspector/chips — no surface in the new editor) and the
.plan-* plan-strip styling (plan UI is gone). Kept the two still-live classes
that share neither prefix's full set: .clip-chars/.clip-char (clip badges) and
.clip.off-plan (removed_from_plan marker).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Composer pt.2 backlog (#6/#7/#8):
- Import now asks Merge (overlay, name-overwrite) vs Replace all (wipe first),
for both shortcuts and split markers. Backend import_* gain a replace flag;
routes read ?mode=replace.
- Delete-all buttons clear every shortcut+category / every split marker, via new
clear_shortcuts / clear_transitions bridge fns + /clear routes.
- New Composer "Files" tab: on-disk file manager over the prompt-library JSONs and
the refinement-key store (keys + sidecars + value/latent .pt) with size, per-file
delete, and per-group delete-all. Path-traversal-safe (basename-only, root-checked).
Deleting library JSONs reloads Shortcuts/Splits so no stale entries linger.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Centralized text styling in OverlayUI (applyTextCss + textStyleControls) shared by
the program monitor, the Add-text modal, and the inspector. Pillow export renderer
honors every prop (real bold/italic faces or faux stroke/shear).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. Image drop → i2v anchor: re-wire S.assignMediaToScene onto the timeline clip
(it was orphaned when the Plan card, its only caller, was removed).
2. Full rewiring in Full control: nodes.core_producers() now enumerates every
installed core node's outputs (Studio model/conditioning/sigmas, sampler
latent/images…), so a custom slot (e.g. a replacement sampler) can source
them via input_sources. core_graph() tags each output with source_id + lists
slot consumers; Models shows all core outputs (tappable in Full control).
3. Preview stuck on the media-bin preview ('anchor'): clear the transient
mediaPreviewId on transport scrub/seek (_seekNow), play (_play), and clip
selection (selectScene) so the program resumes following the playhead.
4. Remove t2v ('empty') as a selectable scene source; default new scenes to i2v
anchor. Anchorless scenes still fall back to t2v in the engine.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Scene properties: 'More editing' → slim 'Scene options'; Frames/FPS hoisted
to the always-visible body; effects (blur/fade/zoom) dropped here (now applied
from the timeline + Add → Effects, same scene.effects); source trim, i2v
guides, and Exclude kept.
- Composer: shortcut editor modal now stacks above the floating window (z-index).
- Composer button restyled to match the neighbouring panel tabs.
- Categories/sub-categories: persisted managed list (nested) in templates.py;
shortcut editor uses Category/Sub-category dropdowns with inline '+ Add new…'
mini-modals; Shortcuts '+ Add' is now a dropdown (Shortcut/Category/Sub-category).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- templates.py: persist category + sub_category on each shortcut (normalized,
free-text; sub-category nests under category).
- Composer shortcut editor: Category + Sub-category inputs; list rows show a
"category · sub" tag and filter on it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Composer button moves next to the Assets/Preview/Properties tabs (was in the
menu bar).
- Remove Characters entirely from the UI (Composer tab + the scene inspector's
character chips). Backend character code left dormant.
- New "Compose" tab hosts the Global prompt (moved out of the inspector); stays
in sync with the timeline, never clobbers active typing.
- Properties decluttered: drop Video transition, Blend length, Generation split
marker and "Pause before next clip" from a scene, and "Split before scene 1"
from the project — these are driven by the global prompt and timeline.
- Swap Import/Export arrows in Shortcuts & Splits (Import ↓ "to me", Export ↑).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Strip the five non-media tabs from the left panel — it shows projects + the
media bin and nothing else:
- Characters / Shortcuts / Split markers already live in the Composer window.
- Effects / Transitions are edited per-clip in the Properties inspector
(Video effects + Outgoing seam), so the preset-apply tabs were redundant.
Removed the orphaned tab renderers and their helpers (editors, modal,
search/filter, characterForm). Updated the welcome tour: the old "Media
libraries / .bin-tabs" step now points at the Composer button, and the
split-marker hint points to the Composer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reshape the editor frame to OpenCut's layout (Assets | Preview | Properties
over a full-width Timeline), keeping every panel's mount point so existing
modules keep working:
- index.html: single horizontal main-row of three resizable columns; preview
is the flexible center, Assets/Properties flank it. Plan strip removed
(recipe now lives in the Properties column per the one-timeline model).
- styles.css: new workspace layout + graceful column-toggle degradation;
floating-window + Composer-button styling.
- app.js / layout.js: rewire splitters (--media-w / --properties-w / --timeline-h)
and column-visibility chips for the new arrangement.
- floating_window.js: reusable OS-style window (drag / minimize-to-pill /
maximize / close); foundation Models & Engine settings can adopt later.
- composer.js: Composer window hosting Characters / Shortcuts / Split markers,
toggled by a header "Composer" button.
- remove plan_strip.js (superseded).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'Remove from plan' (plan card) now: if the gen-unit has a render, mark it excluded
(so generation + prompt skip it everywhere — reuses every existing !excluded filter,
no audit) plus removed_from_plan (hide from the plan strip, override the single export
skip so the clip still renders, and skip the 'excluded' dim styling). Ungenerated
scenes are still a plain delete. Inspector shows a 'Restore to plan' banner on an
off-plan scene; timeline marks it with a subtle dashed outline.
This retires ghosts for the plan-removal path: the removed scene persists as a real
result clip in the cut, not a separate ghost. New Scene.removed_from_plan field
(persisted). Backend tests green (62).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The timeline is the result; its order is now project.timeline_order (scene ids),
which plan reorder never touches. buildPreviewSegments + _renderClips (export) read
this cut order via orderedTimelineScenes(); it's self-healing (drop gone ids, append
new scenes in plan order) and maintained on load + structure change, with a backend
field so it persists.
- Menubar 'Move Clip Left/Right' reorders the CUT (moveTimelineClip → timeline_order).
- Plan-card '← Plan / Plan →' reorders the PLAN (moveScene → scenes / generation order)
without moving the timeline clip.
- view_bus fingerprints include timeline_order so a cut reorder re-renders.
Reordering the plan no longer moves or alters already-generated videos on the
timeline. Backend tests green (62). Empty timeline_order falls back to plan order
(back-compat). Still TODO: 'remove from plan keeps the clip on the timeline'
(ghost retirement) — entangled with generation/export skip semantics, do next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The timeline is the rendered result, so setting up a generation must not touch it.
Removed the clip-level anchor drop and the timeline track's image->anchored-scene
drop. Image media now drops onto Plan cards (sets that scene's i2v anchor) or onto
empty Plan space (creates a new anchored scene). Video clips (finished results)
still drop on the timeline track. Plan-only image filter; teal drop-target hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes untying plan edits from the rendered result on the timeline. The clip
is the RESULT, so it now labels itself from the frozen render prompt
(renderPromptForScene) instead of the live scene text — editing a scene's prompt
in the plan no longer mutates the already-generated clip's identity here (it just
shows 'edited' until you regenerate). Falls back to live plan text only when the
scene isn't generated yet. Video clips unchanged.
With the earlier immutable-length fix, a plan edit (prompt/length) now leaves an
existing generated video's content, duration, AND label on the timeline alone;
only regeneration updates it. Editorial props (effects/trim/audio) stay live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New plan_strip.js band (#plan-zone) above the timeline: compact recipe cards
(anchor thumb + prompt + status: not generated / in sync / edited), click to
select, chevron opens a full-width inline editor (prompt, source, anchor picker,
Generate this scene, Remove, Advanced -> inspector). Collapsible. Placed above
#split-bottom so the timeline splitter / --timeline-h math is untouched; it's a
bounded, additive band (dock flex:1 absorbs it).
Plan = 'what will generate'; the timeline below is the immutable rendered result.
Edits are quiet/debounced and the band won't rebuild while a field is focused
(no input disruption). Selection syncs with the timeline.
Phase 1 / additive: the inspector's per-scene panel is intentionally kept as the
full editor + fallback (the plan can be Hidden) — retiring/merging it and removing
ghosts are the next steps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The convert verbs don't fit the revamped plan/result direction and need a
rethink (see deferred notes). Remove the toolbar buttons now and the two
inspector hints that pointed at them, so nothing references a control that's
gone. The convertToVideo/convertToScene store functions are left intact and
callable — only the UI entry points are removed; the rework comes later.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A render never recorded the length it was actually made at — sceneDurationSec
recomputed from the LIVE plan every time, so changing project/scene length
retroactively truncated already-generated videos and made their tail unreachable
in the preview, player, and final export alike.
Now each render freezes its real length at generation time (durationSec). A
generated scene in project (default) frames mode plays/lays-out/exports at that
frozen length; the project/scene default only drives the NEXT generation.
Explicit per-scene length (timeline/custom), slip-trim, and splits still win
(a split clears the frozen length so both halves revert to plan layout).
- store: renderDurationSec + _planDurationSec; sceneDurationSec is render-aware;
_recordSegment freezes durationSec on scene renders and ghosts; trim/resize
measure from the shown length; segmentDurationSec delegates ghost length to
_ghostDurationSec (de-dupes the inline calc).
- inspector: scene duration hint shows 'Rendered ~Xs - plan Ys - Regenerate to
apply' when a render is frozen at an older plan length.
Centralized in sceneDurationSec so timeline, player, audio offsets/durations and
the final stitch all agree. No-op in the common case (plan == render). Renders
made BEFORE this change have no frozen length and stay plan-driven until
regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exposed node controls (e.g. a LoRA strength in project settings) now carry the
node's actual min/max/step/default from object_info instead of step=1/no bounds:
- capture numeric widget meta at expose time and on link creation (models.js)
- apply it in the Models node editor and the inspector's Exposed controls
- backfill meta onto pre-existing exposures when Models is opened / refreshed
Typing an exposed value no longer gets disrupted: the field now carries a data-k
so the edit guard protects it, and setModelInput/setModelLink update in place +
debounce the server save (was one saveModels POST per keystroke, notifying twice
and rebuilding the field mid-type). Pending model saves flush on blur and before
generate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rebuild guard now protects only typed/dragged fields (text/number/range),
not selects/checkboxes — so dependent controls (custom length input, Ken
Burns knobs, Steer mode 'absolute', Embed/Mid-scene guide) reveal on the
toggle instead of only after blur (inspector.js + engine_settings.js).
- Tag blend-length and video 'Source in' fields so non-quiet patchScene no
longer yanks them mid-type.
- Blur % and Zoom amount % slider labels update live while dragging.
- Surface Width/Height in the Project Output section (out of the collapsed
Advanced fold); leave Max scenes in Advanced.
- Custom scene length reflects its snapped value back into the field on commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With exactly one transition trigger in the prompt (one "cut", one "qcut", etc.),
split_scenes produced anchor + 1 scene — so len(canon_scenes) == 1. The
previous guard `> 1` skipped the clean-text encode path, leaving
output_conditioning built from the raw prompt_to_encode which still contained
the trigger word. The CLIP encoder received "cut" and the model generated it
in audio.
Changing to `>= 1` ensures any split with at least one clean scene uses the
properly stripped scene text, even when there's only one scene to encode.
The chain sampler receiving a single conditioning entry is fine — it just
renders a single pass with no chaining.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Normalization is now available in both Hybrid Euler 2S and Distilled Flow
samplers, making the standalone Normalizing sampler redundant. Removes the
node class, its sampler function, two private helpers (_parse_norm_factors,
_apply_av_norm_factors), the Studio/Movie Editor "Normalizing" type option,
and the defaultNormalizing() factory. Stale "same as the Normalizing sampler"
tooltip references in both sampler nodes are updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>