Commit Graph
1768 Commits
Author SHA1 Message Date
bflatasticandClaude Opus 5.5 0fe6db00e8 M4.9: Preserve frame rate when nested or in render queue, Preserve resolution when nested
Composition Settings ▸ Advanced has both options (they were missing from the model, the
renderer and the dialog):
- Preserve frame rate: nested in another comp, the comp shows only its own frames
  (`EvalCtx::nested_time` floors the precomp layer's source time to the nested comp's frame
  rate; used for the nested frame, its proxy, aux channels, collapsed transformations and the
  viewer's internal wireframes), and the Render Queue renders it at its own rate whatever the
  Frame Rate override (`RenderSettings::rate`).
- Preserve resolution: nested in a comp rendered at Half / Third / Quarter, it renders at full
  size and the buffer's scale places it.
- `comp.settings {preserveFrameRate, preserveResolution}`, `comp.info` reports them, scripts
  read and set `CompItem.preserveNestedFrameRate` / `preserveNestedResolution`, Pre-compose
  copies them, and the dialog registers `dialog.comp.preserveFrameRate` /
  `dialog.comp.preserveResolution`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:33:14 +07:00
Brandon ThomasandClaude Opus 5.5 e910858445 Never crash: PostScript radix numbers outside 2..=36
In EPS/PostScript, `0#7` asked i64::from_str_radix for radix 0, which panics (found by
fuzzing). Radixes outside 2..=36 are not numbers now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:21:14 -04:00
Brandon ThomasandClaude Opus 5.5 fe89ab9146 Never crash: render and gpu crates without panic!()
render: the debug-only quantisation check uses debug_assert! instead of panic!. gpu: the
test-only panic on a wgpu validation error is cfg(test) with a narrow allow, and release
builds only log. Test-only unreachable!() calls become panic!(). Denies the panic lints in both
crates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:20:37 -04:00
Brandon ThomasandClaude Opus 5.5 fc737d76da Never crash: Tick and timecode arithmetic can't overflow or divide by zero
Tick arithmetic saturates, seconds-to-tick conversion clamps to Tick::MIN..=Tick::MAX (NaN is
zero), zero rates/timebases no longer divide by zero, and timecode / feet+frames parsing and
formatting use checked or saturating math. `layer.timing {"delta": -1e308}` over the control
channel overflowed FrameRate::snap_nearest (found by fuzzing every command), and a timecode
field holding 99999999999999:00:00:00 overflowed parse_timecode. Denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:20:33 -04:00
Brandon ThomasandClaude Opus 5.5 5dd7d9bb57 Never crash: effects crate without expect()
Effects without expect(): nested parameter groups are looked up with ? (falling back to the
effect's top level), the OCIO effects use named constants for the built-in ACES spaces instead
of expect()ing a table lookup, curve fitting and Path Text no longer expect on just-pushed
vectors. Test-only unreachable!() becomes panic!(). Denies the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:14:16 -04:00
Brandon ThomasandClaude Opus 5.5 19c1d2ea1d Never crash: reject SVGs nested too deeply
roxmltree recurses per element and overflowed a 2 MiB stack at a few thousand nested
elements (aborting the app). parse() now scans the element depth first and returns
Error::Xml beyond 256 levels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:05:58 -04:00
Brandon ThomasandClaude Opus 5.5 ad5aa542b4 Never crash: reject timeline documents nested too deeply
FCP 7 XML / FCPXML nested about 1 000 elements deep overflowed an 8 MiB stack in the XML
parser (aborting the app). Import now scans XML and OTIO JSON nesting first and returns
Error::TooDeep beyond 256 levels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:05:57 -04:00
Brandon ThomasandClaude Opus 5.5 d1413d5209 Never crash: bound PSD descriptor nesting
The descriptor depth limit restarted at 0 inside every nested descriptor (Objc / GlbO /
ObAr), so a crafted file of 100 000 nested descriptors (2.4 MB) recursed until the stack
overflowed and aborted the app. The depth now carries through nested descriptors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:05:55 -04:00
bflatasticandClaude Opus 5.5 fa27d621e0 M4.8: Edit ▸ Purge clears what it names: disk and snapshot purges keep the RAM preview, memory purges drop decoded footage
- Every purge emptied the viewers' RAM preview; Purge ▸ All Disk Cache and Snapshot now leave
  it (Image Cache Memory, All Memory, All Memory & Disk Cache, All and All 3D Cache still
  clear it).
- The memory purges also drop the decoded footage frames (`FootageSource::purge`, the media
  pool's `clear_frames`; decoders stay open), so footage reads decode again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:03:47 +07:00
bflatasticandClaude Opus 5.5 5ce641c6b3 M4.7: Cache Frames When Idle renders the work area in the background
Composition ▸ Preview ▸ Cache Frames When Idle was only a saved checkbox. After a second
without input or edits it now renders the work area's frames (from the current time on, then
from the start) into the RAM preview, a few at a time behind the viewer's own frame, until the
work area is cached or the RAM preview budget is full.

Docs: preferences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:56:49 +07:00
bflatasticandClaude Opus 5.5 6214aecdb9 M4.6: previews: Auto resolution keeps its scale while playing, Cache Before Playback plays what fits, achieved frame rate in the Info panel
- Viewer Resolution Auto rendered at half its scale while a preview played, so frames cached
  while scrubbing were not played and frames cached by the preview were re-rendered once it
  stopped. Auto now follows the zoom alike playing and paused (Fast Previews still lowers it
  while dragging).
- Cache Before Playback waited for every frame of the range: a range larger than the RAM
  preview budget evicted its first frames and never started. The range is now cut to the
  frames that fit (90 % of Settings ▸ Memory & CPU's budget) with a message, then cached and
  played.
- The Info panel shows the frame rate a playing preview achieves and whether it is real time
  (`Playback::achieved_fps` / `real_time`, automation id `info.previewFps`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:52:50 +07:00
bflatasticandClaude Opus 5.5 5648fdb1c2 M4.5: RAM preview: render options in the frame key, least-recently-shown eviction, stale frames freed at once
- `FrameKey` carries a hash of the render options (`frames::opts_hash`): switching Fast
  Previews (Draft / Fast Draft) or Realtime Shadows no longer shows frames rendered the other
  way.
- Eviction drops the least recently shown frame (`Frames::get` touches it), not the oldest
  inserted: a looping preview a little over budget no longer evicts the frame it shows next.
- Frames of earlier project revisions are dropped on every change (`Frames::drop_stale`); they
  could never be shown again but held memory (and GPU textures) until evicted.
- The green cache bars (Timeline, Preview panel) count the frames of exactly what the viewer
  shows — scale, 3D view / region of interest and options (`EffectcraftApp::shown_series`) —
  instead of any frame at that scale.
- Playback loops compute the key's fixed part once (`EffectcraftApp::frame_series`).
- Docs: architecture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:43:56 +07:00
Brandon ThomasandClaude Opus 5.5 786e4e8b97 Never crash: raster and path crates without expect()/unwrap()/unreachable!()
raster: the inpainting pyramid stops at a missing level, and video inpainting falls back to a
plain patch fill if the previous frame is missing, instead of expect(). path: curve fitting of
an empty point list returns a degenerate cubic instead of indexing/unwrapping, and Merge Paths
no longer has an unreachable!() arm. Denies the panic lints in both crates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:41:09 -04:00
Brandon ThomasandClaude Opus 5.5 3d5432c318 Never crash: engine without expect() (job polling, commands, menus)
Removes the engine's production expect() calls: background-job polling (camera, mask, roto,
point and warp tracking, render queue) uses let-else / if-let instead of take().expect("job"),
command helpers bind what they checked instead of re-reading it with expect, the menu bar falls
back to an empty tree (logged) instead of expect()ing that a constant parses, and
autotrace/text animator group lookups no longer expect a just-inserted group. perf.rs, the
benchmark harness behind `effectcraft-cli bench --ops`, opts out with a module-level allow.
Test-only unreachable!() calls become panic!() (clippy.toml allows panics in tests). Denies the
panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:40:46 -04:00
Brandon ThomasandClaude Opus 5.5 0873393e43 Never crash: text layout and font resolution without expect()
Text layout and font resolution without expect(): an empty line list keeps the default bounds,
the line breaker stops instead of expect()ing a checked option, and font resolution falls back
to the first bundled face. The one remaining expect (the bundled-font database is never empty)
carries the documented narrow allow. Denies the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:27:38 -04:00
Brandon ThomasandClaude Opus 5.5 6144ca0804 Never crash: last-resort guard around command execution, and a logging panic hook
Session::execute runs every command (UI, CLI, control channel, MCP, scripts; file import,
export and save are commands too) through guard::guarded, which catch_unwinds a panic that
escapes the command and returns EngineError::Other("command `x` failed with an internal error
(...); it was stopped and your project is still open"). Edits work on a copy of the project, so
a command that panics mid-edit leaves the project untouched. The desktop app also installs a
panic hook that logs every panic through the app logger (then runs the default hook).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:27:33 -04:00
Brandon ThomasandClaude Opus 5.5 f8460971f0 Never crash: scripts nested too deeply return an error instead of overflowing the stack
Scripts (Script Console, effectcraft-cli script, the MCP run_script tool) are parsed by Boa,
which recurses per nesting level and overflows the stack (aborting the app) at about 200
levels on an 8 MiB thread. run() now rejects a script nested deeper than MAX_NESTING (96)
brackets with a ScriptError before parsing; strings, template literals and comments are
skipped. Also replaces expect("console context") with an error, and denies the panic lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:26:05 -04:00
Brandon ThomasandClaude Opus 5.5 bf8962becf Never crash: trackers and camera solver without expect()
The camera solver, KLT pyramid and subspace (warp stabilizer) tracker expect()ed data-structure
invariants: a track point seen in a frame, a solved camera, a pyramid level. A missing value now
skips that point/track (or, for finish(), returns a SolveError: "no frame was solved") instead of
panicking. Denies the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:24:17 -04:00
Brandon ThomasandClaude Opus 5.5 1da7dc4659 Never crash: Lottie enum values can't overflow
Lottie stores line cap, line join, merge mode and some text-selector enums 1-based, and the
importer subtracted 1 from whatever number the file held: -1e308 (rounded to i64::MIN)
overflowed, a panic in debug builds (found by structural fuzzing). Use saturating_sub. Denies
the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:24:14 -04:00
Brandon ThomasandClaude Opus 5.5 5cc376a818 Never crash: Opus and VP9 encoders without expect()
opusenc: the SILK state is used through if-let / let-else instead of expect("SILK state").
vp9enc: the motion-search SAD and inter prediction return early without a reference frame
instead of expect("reference"). Denies the panic lints in both crates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:23:06 -04:00
Brandon ThomasandClaude Opus 5.5 da3c7a842d Never crash: HEVC encoder without expect()/unreachable!()
Replaces the encoder's 8 expect()/unwrap()/unreachable!() calls with handling that can't
panic: coding-unit decisions without a candidate report an infinite cost, inter helpers
without a reference picture return a neutral result, residual_coding of an all-zero block
writes nothing, the impossible Intra arms are no-ops, and the edge padding of an empty row uses
0 instead of unwrapping. Denies the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:23:03 -04:00
Brandon ThomasandClaude Opus 5.5 ee90d87742 Never crash: AV1 encoder without expect()/unreachable!()
Replaces the encoder's 11 expect()/unreachable!() calls on internal invariants with handling
that can't panic: the rate-control state is passed in rather than re-borrowed, inter decisions
without a reference frame fall back to intra, per-plane results are bound directly, and the
writer's impossible branches become debug_asserts. Denies the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:22:59 -04:00
Brandon ThomasandClaude Opus 5.5 fc8fea031c Never crash: reject deeply nested expressions instead of overflowing the stack
An expression nested about 60 brackets deep overflowed the stack of a 2 MiB thread in the
JavaScript parser (release and debug; ~200 levels on 8 MiB), which aborts the whole app: it
can't be caught. The array-maths rewrite also nests one call per arithmetic operator, so a long
sum went the same way, and the rewriter itself recursed per bracket. rewrite() now measures the
nesting (brackets plus chains of prefix operators) before and after rewriting and turns an
expression deeper than MAX_NESTING (32) into a RangeError; check_syntax reports the same error.
Also replaces the unreachable!() in the bracket-tree builder with a let-chain, and denies the
panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:22:36 -04:00
BFlat 249cc145e3 Merge pull request #8 from storybored379/pr/fullscreen-shortcut-off-mac
Shortcuts: don't bind macOS-only Ctrl+Cmd shortcuts off macOS
2026-10-05 00:16:09 +07:00
bflatasticandClaude Opus 5.5 948f06fbf2 M3.14: project files name the version that wrote them, never save unreadable, keep their format
- `Project::to_file_json` writes `savedBy` (the EffectCraft version) ahead of the project and
  checks that the text reads back before anything is written: JSON has no NaN or infinity, so
  such a value made a file that saved but could never be opened. Save now fails with a message
  and the file on disk is left as it was.
- Opening a project saved by a newer EffectCraft warns that settings this version doesn't know
  are lost on saving; `file.open` reports `savedBy`. A file that isn't a project names itself in
  the error.
- Increment and Save keeps the extension (`.ecprojx` stays XML); Save a Copy writes XML for
  `.ecprojx` / `.xml` paths, like Save As.
- Docs: architecture (document model), parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 00:08:07 +07:00
Brandon ThomasandClaude Opus 5.5 587d4803fe Never crash: project crate without expect()
Layer::motion_trackers_mut expect()ed the group it had just inserted; it returns an Option
now and track.motion reports an error if the group is somehow missing. Denies the panic
lints in the crate (the project model behind .ecproj files).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:58:51 -04:00
bflatasticandClaude Opus 5.5 6fc39eb133 M3.13: menu bar: Group/Ungroup Shapes in the Layer menu, Window ▸ Learn, flowchart shortcuts, Center In View shortcut, Reveal in Explorer on Windows
- Group Shapes (Ctrl+G) / Ungroup Shapes (Ctrl+Shift+G) sit in the Layer menu itself, as in
  After Effects (they were under Mask and Shape Path).
- Window ▸ Learn opens the Home screen's Learn tab.
- Composition ▸ Composition Flowchart is Ctrl+Shift+F11 and Window ▸ Flowchart Ctrl+F11 (the
  flowchart had the panel's shortcut and the panel none).
- Layer ▸ Transform ▸ Center In View shows Ctrl+Home.
- "Reveal in Finder" reads "Reveal in Explorer" on Windows and "Reveal in File Manager" on
  Linux (`menus::platform_label`).
- Docs: parity (View ▸ New Viewer noted as not yet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:58:00 +07:00
bflatasticandClaude Opus 5.5 a34d0c421b M3.12: Pre-compose keeps the composition's settings; New Comp from Selection is one undo step with After Effects' options
- Pre-compose makes the new composition with the original's pixel aspect, background, motion
  blur and frame blending settings and 3D renderer (`Comp::nested_like`); it kept only size,
  rate and background.
- File ▸ New Comp from Selection records one undo step (two per item before) and takes each
  item's pixel aspect. Several items selected open the New Composition from Selection dialog:
  Single / Multiple Compositions, Use Dimensions From, Still Duration (default from Settings ▸
  Import ▸ Still Footage), Add to Render Queue, Sequence Layers with Overlap, Duration and
  Transition. `file.newCompFromSelection {single, dimensionsFrom, duration, addToRenderQueue,
  sequence, overlap, overlapDuration, transition}`; `layer.addItem {duration}` for stills.
- Docs: parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:48:54 +07:00
bflatasticandClaude Opus 5.5 89653f37de M3.11: layer commands respect locks, keep children in place on delete, arrange step by step, keep depth and separated dimensions, keep links on duplicate/paste
- Locked layers can't be selected (Timeline click, Select All, Ctrl+Up/Down; shy layers while
  hidden too) and are left alone by Clear/Delete, Arrange, Layer ▸ Transform and Center Anchor
  Point, which fail when every target is locked (`commands::unlocked`, `commands::selectable`).
- Deleting a parent unparents its children where they are, in the same undo step: the
  pick-whip compensation moved out of `layer.setParent` into `layer::parent_fixes` /
  `ParentFix::apply`, which now also handles separated X/Y Position.
- Bring Forward / Send Backward move each selected layer one step (a non-contiguous selection
  was squeezed into one block).
- Center In View / Fit to Comp keep a 3D layer's Z (and Fit keeps Scale/Anchor Z); Reset /
  Center / Fit / Center Anchor Point work with separated dimensions (`layer::update_position`).
- Duplicate and paste keep parents and track mattes, following the copies when both were
  copied; paste dropped them always.
- Docs: agents, parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:33:54 +07:00
Brandon ThomasandClaude Opus 5.5 dc573f77e6 Never crash: PSD reader bounds overflow and input-sized allocations
Layer bounds come straight from the file as four i32s. Rect::width/height subtracted them in
i32 (overflow: a panic in debug builds, garbage sizes in release), and layer_pixels allocated
width x height pixels before reading any data, so a 100-byte file claiming a 2^31-square layer
hit "capacity overflow" (or an out-of-memory abort). Widths are now computed in i64 and
clamped, every decoded image is capped at 2^28 pixels (Error::Unsupported beyond that), zlib
data is inflated with a limit of the expected size, RLE count tables and UTF-16 strings
reserve no more than the remaining bytes, and the per-pixel copy only visits the part of the
layer that lands in the output. Denies the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:27:18 -04:00
bflatasticandClaude Opus 5.5 753de070a0 M3.10: Layer Settings opens Solid Settings with "Affect all layers that use this solid"; settings dialogs register every control
- Layer ▸ Layer Settings on a solid or adjustment layer opens Solid Settings on the layer's
  solid (it ran an empty command before): name (renames the layer and the solid), size, Pixel
  Aspect Ratio, colour, and "Affect all layers that use this solid". It starts off, as in After
  Effects, so changing a solid other layers share gives this layer its own copy.
  `layer.settings {pixelAspect?, affectAll?}` → `{layer, solid?}`; `layer.newSolid {pixelAspect?}`.
- Nulls ask for their name; text and shape layers have no settings (the entry is disabled).
- Composition Settings / New Composition and Solid Settings register an automation id for
  every control (`dialog.comp.*`, `dialog.solid.*`). Composition Settings takes any typed frame
  rate besides the list, and New Composition starts from the last settings used.
- Colour buttons in both dialogs show the stored sRGB colour (`widgets::srgb_color_button`);
  egui's linear picker showed it lighter.
- Docs: agents, parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:22:09 +07:00
Brandon ThomasandClaude Opus 5.5 37c9b449c6 Never crash: media crate without expect()
The smart-object branch of the layered-file decoder bound the footage layer twice and
re-read it with expect("embedded implies a layer"); bind it once with a let-chain. Denies the
panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:18:36 -04:00
Brandon ThomasandClaude Opus 5.5 8e4e29844e Never crash: PDF close-path operators and CFF outlines without a current point
The s, b and b* operators closed the path even when there was no current point, which kurbo
asserts against (a panic in debug builds; found by fuzzing). Close only when a subpath is open.
CFF charstrings that draw before their first rmoveto now start the subpath at the current
point, as Type 1 charstrings already did. Denies the panic lints in the crate (it had no panic
sites).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:15:02 -04:00
Brandon ThomasandClaude Opus 5.5 6708ff55b4 Never crash: SVG path data no longer loops forever on a number after Z
Path data like "M0 0 L10 0 Z9" repeated Z implicitly for the stray number; Z consumes no
numbers, so the parser looped forever appending close-paths until memory ran out (a 1-byte
change to a valid SVG used 24 GB and was killed). A number after Z is a syntax error, so
parsing now stops there and keeps what was read, as the spec asks. Also denies the panic lints
in the crate (it had no panic sites).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:14:58 -04:00
Brandon ThomasandClaude Opus 5.5 ed54a8cd15 Never crash: interchange export/import without expect()
Removes the six expect() calls from timeline export/import and denies the panic lints in the
crate. A precomp layer whose source item is not a composition (a damaged project, or an item
that was deleted) made export panic in Exporter::sequence; it now leaves that layer out.
Exporter::sequence and track_item return Option, the Motion/Opacity/Volume effect defaults
come from effect_or_new (None if FilmCraft's registry lacks one), and import's motion() skips
a layer without a transform group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:10:09 -04:00
bflatasticandClaude Opus 5.5 e0ed28f55d M3.9: closing a modified project asks to save it first; the modified mark follows undo
- Quit and the window's close button, File ▸ New Project, Open Project, Open Recent, Close
  Project, the demo project and a Home template show Save / Don't Save / Cancel when the
  project has unsaved changes (Save asks for a path when untitled; cancelling that keeps the
  project open). File ▸ Revert asks to discard. Before, all of them dropped unsaved work
  without asking.
- The window title shows the project's path and `*` while it is modified.
- The modified mark follows undo: the session keeps the snapshot it last opened or saved
  (`Session::saved_project`), so undoing, jumping in the History panel or rolling back a batch
  to the saved state clears it.
- An edit that changes nothing (a value set to what it already is) records no undo step and
  doesn't mark the project modified (`Project::same_content`, comparing untouched comps by
  pointer).
- Agents' `engine.execute` / MCP calls are never asked; `ui.menu.invoke` shows the prompt like a
  click (automation ids `dialog.unsaved.save` / `dontSave` / `revert` / `cancel`), and the
  control channel's `app.quit {force: true}` skips it.
- Docs: architecture, agents, control protocol, parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:02:14 +07:00
storybored379andClaude Opus 5.5 0a8cdfc67e Scripting: Property.keyInSpatialTangent / keyOutSpatialTangent under their AE names
The getters were registered as keySpatialInTangent / keySpatialOutTangent,
so scripts using the documented After Effects names failed with
"TypeError: not a callable function" (setSpatialTangentsAtKey and the
other keySpatial* methods already use AE's names).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:17:34 -07:00
storybored379andClaude Opus 5.5 3411fc80ab Keyframes: a Hold key no longer eases the motion into it
segment_value treated any non-Linear in-side of the next key as Bezier,
so a key set to Hold (KeyframeInterpolationType.HOLD, add_keyframe
"interpolation": "hold", or the Keyframe Interpolation dialog, which set
both sides) turned the linear segment into it into an ease-in with the
default ease: 0 -> 100 over 2 s read 27.0 instead of 25 at 0.5 s. Hold
only affects what follows the key, which the out side already handles.
The Graph Editor and the Lottie exporter already treat a Hold in-side as
linear, so exports didn't match the preview either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:17:34 -07:00
storybored379andClaude Opus 5.5 18fcc48046 Shortcuts: don't bind macOS-only Ctrl+Cmd shortcuts off macOS
Off macOS `Cmd` maps to Ctrl, so Enter Full Screen's Ctrl+Cmd+F parsed
to the same keys as Find's Cmd+F and both commands were bound to Ctrl+F
(ui_and_menu_shortcuts_do_not_collide fails on Linux and Windows).
Shortcuts that use both Ctrl and Cmd can't be typed distinctly there,
so they are skipped when the key bindings are built off macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:17:34 -07:00
Brandon ThomasandClaude Opus 5.5 a006ed5765 M13.32: web main-thread stall: Warp Stabilizer plans solved in job workers
warp.status solved the stabilization plan (Subspace Warp eigen-decompositions and mesh fits)
on the page the first time it saw an analysis: ~4.9 s blocking the browser's event loop in
the smoke test. The analysis job now sends the plan summary with its result, and a settings
change has a WarpPlan job solve it (status reports stabilizing meanwhile). Desktop unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 03:40:46 -04:00
Brandon Thomas f9e1807962 Merge branch 'main' into worktree-agent-abff9a10f06a98f2c
# Conflicts:
#	crates/gpu/src/tests_deferred.rs
2026-10-04 03:16:01 -04:00
Brandon ThomasandClaude Opus 5.5 3921e6c680 M13.30: Content-Aware Fill in a job worker on its GPU
contentFill.generate sends its plan (FillPlan) to a browser job worker:
the layer's frames render in passes on the worker's WebGPU device, the PNG
sequence goes back to browser storage (Services::store_file), and the page
adds the fill layer when the plan returns (WorkerReply::Fill). The smoke
test checks it; docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 03:15:54 -04:00
Brandon ThomasandClaude Opus 5.5 c59e0a6407 Merge main into M13.28 worktree
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:57:55 -04:00
Brandon Thomas 91f4a603a9 Merge branch 'main' into worktree-agent-abff9a10f06a98f2c
# Conflicts:
#	docs/parity.md
2026-10-04 02:57:35 -04:00
Brandon ThomasandClaude Opus 5.5 f3715aac32 M13.30: web depth and polish
- GPU in browser job workers: each job worker opens its own WebGPU device
  (deferred readbacks); the Render Queue export and the analyses' frame loops
  are futures (render::passes, offload::run_request_async) whose frames
  render in passes, awaiting the device between them. GPU particles and
  Advanced 3D read back under keys too. workers.jobs reports passes and
  readbacks; the smoke test measures them.
- Layer buffers in the browser's OPFS disk cache: frame workers back their
  layer caches with a PrefetchStore; the page plans what to fetch before a
  request from recent misses (LayerPrefetch over the shared DiskIndex).
- Templates embed footage (default on, size-capped with a warning) and
  creating from a template extracts it next to the new project / into
  browser storage.
- ICC LUT profiles (lut8/lut16/lutAtoB/lutBtoA, from the public ICC spec) in
  Simulate Output > My Custom RGB, baked into a 3D LUT for the viewer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:57:17 -04:00
Brandon ThomasandClaude Opus 5.5 c4315818da M13.28: GPU generators (fx_gen2): Lightning, Advanced Lightning, Beam, Lens Flare, Radio Waves, Vegas, Stroke, Scribble, Write-on, Paint Bucket, Eyedropper Fill, CC Glue Gun, CC Threads, Audio Spectrum/Waveform, Basic Text, Path Text
The geometry stays on the CPU as per-frame plans shared with the CPU effects (bolts, mask
strokes, scribbles, contour segments, audio marks, glyph strokes, waves, flood-fill region,
eyedropper sample); segments and convex polygons are tile-binned and rasterised on the GPU
with the CPU's coverage functions. CPU output unchanged (gen_golden). CC Threads' exact ties
at round parameter values are a documented exception; Fractal stays on the CPU (needs f64).
Regenerated docs/effects.md, docs/parity.md EFF-5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:56:00 -04:00
Brandon ThomasandClaude Opus 5.5 1c855c3e07 M13.29: GPU effects part D (particles and remaining fallbacks)
GPU render passes for CC Particle World / Systems II, Particle Playground,
CC Ball Action, CC Pixel Polly, CC Scatterize and Curl Noise (fx_particles,
chunked multi-pass raster); Shatter wireframe views, Foam extras, Timewarp
with a Matte Layer, strong Warp Fisheye/Twist (CPU f64 inverse map) and
custom .ocio configs no longer fall back to the CPU. CPU refactors pinned by
particle_golden / timewarp_golden / sim_golden hashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:35:56 -04:00
Brandon ThomasandClaude Opus 5.5 5df9cde182 M13.28: GPU pixel ports (fx_pixel2): CC Cross/Radial/Vector Blur, Reduce Interlace Flicker, CC Composite, Color Link, Cineon, HDR Compander/Highlight Compression, Grow Bounds, CC Overbrights, CC Block Load, CC Burn Film, CC Glass/Plastic, CC HexTile, CC Mr. Smoothie, Inner/Outer Key, CC Simple Wire Removal, Basic 3D
Inner/Outer Key's trimap and Cleanup strokes, Color Link's sample and CC Glass/Plastic's
light are shared with the CPU effects (CPU output unchanged: gen_golden). CC Plastic now
reads its Bump Layer (it read the nonexistent bumpMap id).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:33:54 -04:00
Brandon ThomasandClaude Opus 5.5 764c3a31ad M13.28: golden hashes of the plan-drawn CPU effects (pinned before the GPU refactor)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:06:40 -04:00
Brandon ThomasandClaude Opus 5.5 b9f09869eb M13.29: particle effects golden hashes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:04:43 -04:00