* fix(ui): Save Arrangement refuses an unknown bin (#3774)
The engine lays out the root bin when it does not find the one asked for, so
a Save Arrangement for a bin that does not exist (a scripted call, or a bin
deleted while the dialog was open) could overwrite a root arrangement of the
same name. The dialog now refuses a bin that does not exist when it opens and
checks it again before saving.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): Save Arrangement refuses a bin that is not a bin id (#3774)
A `bin` such as -1, a string or 1.5 is not an unsigned id, so it was read
as no bin and the dialog opened for the shown view. Any non-null `bin` that
is not a bin id is now refused; an omitted or null one still means the shown
view's. The test checks the arrangements list it meant to (`arrangements`),
and covers these values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#3749 made opening a clip in Source clear another panel's maximize, so the
test's Media Browser was left too narrow to show the Audio Info column.
Assert the new behaviour, then maximize the browser again.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(graphics): Style buttons show and toggle the Type-tool selection
With characters selected by the Type tool, the Faux Bold, Faux Italic,
All Caps, Small Caps, Underline and Strikethrough buttons showed the
layer's value, not the selection's. A click wrote the inverse of the
layer value to the selection, so a second click could not turn a style
off again.
The buttons are now on when every selected character has the style
(its own override, else the layer's value). New
graphic_design::range_styles lists the overrides of a character range.
generated with Claude Code
* fix(graphics): Style buttons show the layer's style without a selection
Without a Type-tool selection the list of selected characters is empty,
and "every selected character has the style" was true for it. So an
unstyled layer showed Faux Bold, Faux Italic, Underline and
Strikethrough as on, and a click wrote false. The buttons now show the
layer's value when no characters are selected (found by CodeRabbit).
generated with Claude Code
* perf(project): range_styles builds only the selected range
The Style row reads the selection's styles on every redraw, once per button.
range_styles expanded every character of the text and then sliced; it now
allocates only start..end and merges the runs that overlap it, in their
order. A test compares it with the expanded text for every range, including
overlapping and out-of-range runs; the caret and past-the-end cases are
tested too.
generated with Claude Code
* test(graphics): cover Small Caps and a mixed-caps selection
The selected-characters test toggles Small Caps too, and the mixed-selection
test makes "Dr" All Caps before extending to "Drive", so both caps buttons
are off and a click makes all of it All Caps.
generated with Claude Code
* History: each step shows the icon of the tool or command that made it
Each undo and redo step now keeps the id of the command that made it,
through undo, redo, merged and grouped steps and the history limit.
The History panel paints that command's icon before each label: a
timeline tool's icon for the commands the tool runs, FilmCraft's own
icon for other commands that have one, and a plain square for the rest
and for edits made outside a command. Undone steps draw their icon dimmed
like their labels. The Open row has no icon. history.list is unchanged.
* History: ripple trims show the Ripple tool's icon
A ripple trim's undo step records its own id, timeline.trim.ripple,
whether it comes from a Ripple tool drag, a ripple edit point in trim
mode or a dynamic ripple trim, and the History panel shows the Ripple
tool's icon for it. Trim mode nudges, Extend Edit to Playhead and dynamic
trims record the edit point's kind (timeline.trim, timeline.roll or the
ripple id) instead of the outer trim command, so a regular trim shows the
Selection tool's icon and a roll shows the Rolling tool's icon. Trim
Previous and Next Edit to Playhead show the Selection tool's icon.
* History: icon targets on a part-scrolled row match the drawn icon
The History panel registered each row's icon target from the row rect after clipping it to the scroll area, so on a row partly scrolled out the target moved off the drawn icon, and an icon scrolled out entirely was still registered. The target is now the drawn icon's rect clipped to the scroll area, registered only while part of it shows.
* History: Apply Default Transitions to Selection shows the plain square
The trim. family gave trim.applyDefaultTransition the Rolling tool's icon, although the command adds transitions at the selected edit points and makes no rolling edit. It now shows the plain square, as the sequence transition commands do.
* app: focus the Source monitor when a clip is opened in it (#3669)
Opening a clip in Source activated its tab but left focus on the Project
panel, so Space still acted on Project and did not play the clip. The
OpenSource event now also focuses Source.
Tested by opening_a_clip_in_source_focuses_it_so_space_plays_it.
Fixes#3669
* app: reopen the Source monitor before focusing it (#3669)
* ui: say where the project was saved in the status bar (#3599)
File > Save, Save As and Save a Copy gave no feedback besides the Edited mark
going away. They now set the status bar to "Project saved to <path>" (or
"Copy of the project saved to <path>"); the Save As dialog path reports its
errors there too.
Tested with a headless UI test over the control channel: the status was empty
after each save before the change.
Fixes#3599
* ui: Save All also says where the project was saved (#3599)
* fix(ui): the Project panel's Zoom sizes List thumbnails and the Freeform canvas (#3774)
The footer's Zoom slider only sized Icon view's cards. In List view it now sets
the thumbnail size (the row height follows it) when Thumbnails are shown, and
is disabled when they are not, as there is nothing for it to size. In Freeform
view it scales the canvas: cards keep their stored positions and sizes, are
drawn scaled, and a drag moves a card by its distance on the unscaled canvas.
The default size draws both views as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): a zoomed List thumbnail keeps room for the item's name (#3774)
With large thumbnails the thumbnail covered the name and ran into the next
column. The Name column is now drawn wide enough for the thumbnail and
120 points of name; its saved width is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): zoomed Freeform commands use the width the cards are drawn at (#3774)
The card menu (Stack, Unstack, Clip Size), Align to Grid and Save Arrangement
sent no width, so the engine laid unplaced cards out at its 800-point default
and could save positions from different rows than the zoomed canvas shows.
They now send the same unscaled width the view lays the cards out at, and the
engine's parameter lists name the width they already read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): Freeform card and canvas menus act on their own bin (#3774)
A card's menu now stacks, unstacks and resizes only the selected cards of
its own bin, which its canvas width lays out; a selection that also holds
items of another bin left them laid out at this canvas's width. Save
Arrangement saves the bin of the canvas it was opened on, which can be a
floating bin window, instead of the bin the main panel shows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): a project file dropped on the window opens it (#3843)
Dropping a .fcproj on the window sent it to file.import, which does not take
project files, so nothing happened. A dropped project file now opens as
File > Open does; other files dropped with it are not imported, since one
project is open at a time.
Opening a project from the UI with unsaved changes now asks Save / Don't
Save / Cancel first, the prompt Close Project and Quit already show: for a
dropped file, File > Open, Open Recent and the Home screen's recent list.
Scripts (engine.execute) are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): Don't Save keeps the project when the dropped one fails to open
After Save or Don't Save, the open prompt closed the current project before
opening the chosen one, so a file that failed to load (unreadable, not a
project) left an empty project and lost the unsaved changes. Open now
replaces the project only once the chosen one has loaded; file.open leaves
the session clean, as closing it did, so the recovery journal is the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(ui): Save in the open prompt saves a new project, then opens the dropped one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(ui): the dropped-project test works in its own temporary directory
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Export: the time left counts down between batches; the status bar shows it once a second, in place
An export finishes its frames in batches of up to 16. The estimate measured the speed to the
latest reading, so between batches the speed sagged and the time left grew, then fell when the
next batch landed: about ±13 % on a heavy 4K60 export, enough to flip between 1:04:50 and 56:11.
It now measures the speed between batch arrivals and counts down from the last one; a batch three
intervals late falls back to the speed up to now, so a stall still makes the estimate grow.
The status bar redrew its text every frame, right-aligned against the bar, in Inter's proportional
digits, so "Exporting… 46%" moved sideways with every change of the time left. The text now
changes at most once a second, its digits sit in cells of equal width, and its room only grows
while the job runs (from the start it fits 100 % and a time under an hour).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Export: no "0 s left" while the last batch is late; the status text has room for any time left from the start
The countdown between batches reached zero when the last batch took longer than the speed said,
and showed "0 s left" until it was three intervals late; it now gives way to the speed up to now as
soon as it runs out. The status bar's room was for a time under an hour, so an export's first
estimate over an hour still moved the text once; it is now for 99:59:59 from the start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Export: the queue's and the Progress panel's job text change once a second, with digits of one width
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui-egui: tabular_label is read by screen readers as a label
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(sv): translate 49 new strings
Brings the Swedish catalog up to date with upstream main (142fddc1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(sv): translate 55 new strings
Brings the Swedish catalog up to date with upstream main (9c6d6611).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M3: Add shared custom colors and complete marker editing
* #1435: refine native colors and range endpoint flags
Use the macOS Colors panel for custom label colors and reuse the established egui picker on other hosts. Preserve draft confirmation, exact sRGB values, native text shortcuts and project ownership. Compact the swatches and center the add control, split range glyphs across their endpoints, localize the new controls, protect newer marker edits from stale dialogs, and cover the behavior with rendered UI and hit-test regressions.
Co-authored-by: Codex <noreply@openai.com>
* #1435: preserve imported palettes and marker double-click ownership
Union saved OTIO palettes across timelines with deduplication, bounded capacity,
first-name preservation and explicit overflow warnings. Keep null authors
consistent with omitted fields, and consume marker double-clicks before the
work-area gesture so opening the editor does not alter project history.
Correct the CSV URL column and schema documentation. Add six regressions for
palette retention/capacity, author handling, and physical overlapping gestures.
Validation: full cargo xtask ci (3666 passed, 45 skipped), 42 rendered UI tests,
and strict workspace/all-target Clippy on Rust 1.99.1.
Co-authored-by: Codex <noreply@openai.com>
* #1435: retain OTIO swatch names and generator labels
Ignore orphan name entries when retaining saved palette names so input ordering
does not hide valid names after the first 64 entries. Keep names bounded by the
retained palette and preserve existing valid names.
Include generator preset labels alongside custom label colors in OTIO metadata.
Add regressions for orphan-prefixed name maps and three generator kinds with
preset/custom colors through actual export/import round trips.
Validation: both regressions failed before fixes; all 72 interchange tests and
full cargo xtask ci pass (3668 tests, 45 skipped), plus strict workspace/all-target
Clippy on Rust 1.99.1.
Co-authored-by: Codex <noreply@openai.com>
* #1435: preserve distinct OTIO generator item identity
Use exported FilmCraft item IDs for generator media identity so identical
content can retain distinct source names, labels and custom colors. Repeated
references to one source item still share a single imported media entry.
Preserve content-based fallback for missing or invalid item IDs. Cover black
video, bars and tone, and color mattes with distinct items/shared references,
plus legacy and malformed identity metadata.
Validation: distinct-generator regression failed before fix; legacy compatibility
passed. All 74 interchange tests and final full cargo xtask ci pass (3670 tests,
45 skipped); strict workspace/all-target Clippy also passes on Rust 1.99.1.
Co-authored-by: Codex <noreply@openai.com>
* #1435: align Edit Marker layout and add safe marker navigation
Give the editor a wide aligned form, larger Notes area, side action column,
and grouped palette with vertical marker types. Expand the Web Link field
with the form and keep actions accessible in small windows.
Previous/Next saves valid edits as one Undo step and stays with the original
sequence/source owner, including coincident markers. Invalid drafts stay open;
unchanged navigation and Done add no history. Keep native/shared color pickers,
Author, Notes, URL validation and immediate marker creation.
Validation: all 45 rendered sequence/marker UI tests pass, including five new
navigation/owner/validation/layout regressions. Native macOS layout, picker
Cancel and exact single-step Undo restoration verified. Full standard cargo
xtask ci passes 3675 tests (45 skipped), doctests and all remaining gates;
strict Rust 1.99.1 workspace/all-target Clippy also passes.
Co-authored-by: Codex <noreply@openai.com>
* #1435: refine Edit Marker proportions and action alignment
Narrow the dialog to 680 logical pixels and keep the core order Name,
Time/Duration, Notes, Color and marker type. Place Author after the type-specific
options as secondary metadata. Preserve our palette, custom picker and range controls.
Use consistently sized, centered rounded action buttons with a primary Done action.
Shorten Delete Marker to Delete and remove the navigation helper and unused translations.
Keep the existing navigation save/validation/Undo semantics and automation IDs.
Validation: all 45 rendered marker/sequence UI tests pass, including small-window
action accessibility. Actual macOS layout and no-op Previous/Next/Cancel verified.
Full standard cargo xtask ci passes 3675 tests (45 skipped), doctests, formatting,
Clippy, translations, layers, assets and 49 WASM crates. Strict Rust 1.99.1
workspace/all-target Clippy passes separately.
Co-authored-by: Codex <noreply@openai.com>
* #1435: tighten marker layout and validate text editing
Use a narrower aligned form with Author below Name, bounded scrolling Notes,
left-aligned colors, centered actions and an inline Web Link URL/open icon.
Remove the legacy Flash Cue choice while preserving imported marker types.
Constrain timecode typing/paste to complete numeric colon-separated fields,
validate drop-frame labels and frame bounds, and disable Done for invalid timing.
Preserve high-frame-rate timecodes and point-marker semantics when Duration is
unchecked. Route native/menu clipboard and text Undo commands to focused fields;
explicit engine commands retain project Undo and selection semantics.
Validation: standard cargo xtask ci passes 3679 tests (45 standard skips),
doctests, fmt, strict workspace/all-target Clippy, i18n, layers, 69 assets and
49 WASM crates. Also passed 48 rendered marker/sequence tests and strict Rust
1.99.1 Clippy. Actual macOS clipboard, bounded Notes, point save and exact Undo
were checked. Windows/Linux desktop interaction remains pending.
Co-authored-by: Codex <noreply@openai.com>
* M0: Wait for frame-worker recovery before asserting
Do not treat a nonempty request queue as proof of worker death in the
panic-recovery regression. Keep waiting for all injected faults and enough
post-fault completions, then drain the queue without adding more seeks.
Retain the exact fault-count, successful-recovery and empty-queue assertions
and bound the wait with a two-minute deadline.
The inherited test failed twice in independent fork CI. It did not reproduce
locally: baseline passed 20 normal and 10 delayed-report runs; the repair
passed 50 normal and 10 delayed-report runs. No product or marker UI changes.
Validation: standard cargo xtask ci passed all 4485 tests (52 standard skips),
doctests, formatting, strict workspace/all-target Clippy, i18n, layers,
69 assets and 49 WASM crates.
Assisted-by: OpenAI Codex
---------
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <codex@openai.com>