Problem: comp.new/comp.settings clamp sizes to 30,000 px, frame rates to
0.001-1000 fps and motion-blur samples to 2-64, but reading a project
(.ecproj and .ecprojx, templates, recovery, offload) accepted any value:
a 4-billion-px comp, solid or footage frame, a 0 or 10^18 fps rate
(zero-length frames), a negative duration or 4 billion blur samples all
reached the renderer. New Solid, Layer Settings, Import Solid/Placeholder,
Crop Comp and PSD import set sizes without the upper bound, and comp
sizes past 2^32 wrapped.
Fix: one set of limits in effectcraft_project::limits. Project::from_json
brings every item (and proxy) within them; from_json_repaired also says
what it changed, which file.open returns as `repaired` and shows in a
toast, so the project still opens. Project::add_item applies the same
limits to every new item, and the size-setting commands clamp with the
shared helpers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.import stores a path as given, so footage imported by the CLI or an
agent as `clip.mp4` was saved relative, and opening the project looked it
up from the working directory. The CLI, started in the project's folder,
found it; the desktop app, started from the Finder, Explorer or a launcher
in another folder, showed and rendered neither its picture nor its sound,
while the Project panel still listed the saved duration.
On open, a relative footage path that doesn't exist from the working
directory but does next to the project file is resolved there (After
Effects also looks for footage relative to the project). Paths that exist
as they are, and absolute ones, are kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
The UI behaves as in After Effects (the Import dialog's Sequence checkbox
starts off, dropped files are stills, dropped folders make sequences), but
file.import without sequence or drag keeps importing a numbered still's
run, so existing scripts and agents keep working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
After Effects' `app.project.workingGamma` (2.2 or 2.4, ignored with a working space) sets
the curve an unmanaged project uses: at 2.2 the piecewise sRGB curve, at 2.4 a pure 2.4
power curve. EffectCraft always used sRGB, so linear EXRs (Interpret As Linear Light) from
a 2.4 project came out darker in the shadows (0.18 → 0.461 instead of 0.490).
- `ProjectSettings::working_gamma` (default 2.2, serde default for old projects).
- The colour pipe's unmanaged curve is the Working Gamma's (sRGB, or Rec. 709's pure 2.4
curve): Interpret As Linear Light encodes untagged footage with it and Blend Colors Using
1.0 Gamma linearizes with it; it is part of the pipe's cache key. The GPU path takes the
same conversions.
- `file.projectSettings {workingGamma: 2.2|2.4}` (other values are refused, as in AE),
`app.project.workingGamma` in scripts and a Working gamma choice in Project Settings.
Test: render test with linear 0.051 / 0.18 / 0.5 footage (0.290 / 0.490 / 0.749 at 2.4,
0.250 / 0.461 / 0.735 at 2.2, the values measured in AE), linear blending at 2.4, a working
space ignoring the setting, and the cache key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
Cause: SequenceOptions::from_params defaulted `sequence` to true for
file.import, so any numbered still (IMG_0001.jpg) brought its whole numbered
run from its folder, and several picked or dropped stills with a common
prefix became one sequence. Dropped files never asked, and the Import
dialog's "<format> Sequence" checkbox started ticked.
Fix, matching After Effects: file.import takes picked and dropped files as
stills unless `sequence: true` (the Import dialog's Sequence checkbox, which
now starts unticked; Force Alphabetical Order implies it), while the numbered
stills inside a picked or dropped folder still import as sequences
(SequenceOptions::folders), and `sequence: false` (Alt-drag of a folder)
imports a folder's files one by one. Replace Footage, reload, proxies and
render outputs keep detecting their sequence (SequenceOptions::default and
from_params are unchanged), and mediaBrowser.import keeps bringing a numbered
still's run unless `sequence: false`. docs/footage.md and docs/agents.md
describe the new defaults.
Tests: sequence import_takes_stills_unless_asked_for_a_sequence and engine
numbered_stills_import_as_stills_by_default (one dropped still and two picked
IMG_ stills stay stills, a dropped folder makes one sequence, the checkbox
brings the run); the existing sequence tests pass `sequence: true`; ui
import_dialog_offers_the_image_sequence_option (unticked by default: a still;
ticked: the run) and a_dropped_folder_imports_as_one_sequence_with_sequence_interpretation
(two dropped frames: two stills; the folder: one sequence).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
(cherry picked from commit aa3c31db9ab3a774ee2f8ba867e1bafc1fce7aba)
After Effects shows a missing frame of an image sequence as a
placeholder (colour bars) and has no Hold Previous Frame or Skip choice
in Interpret Footage, so EffectCraft doesn't either.
Interpret Footage loses its Missing Frames field, file.interpretFootage
its missingFrames parameter, and Footage its missing_frames setting. A
gap in a numbered sequence's numbering always shows colour bars. A
sequence imported with Force Alphabetical Order still plays its files
one after another (its numbering doesn't count), as in After Effects;
that is now an internal Footage.alphabetical flag, set on import and
kept by Reload. The Interpret Footage note lists the missing frames.
Projects saved before this have no such field and open with the After
Effects behaviour: a sequence with gaps shows placeholders where it
used to close them up.
Test: project sequence unit tests (placeholders, alphabetical order,
projects saved without the flag), tests_sequence (Start Frame instead
of Hold / Skip), sequence_gaps_show_placeholders (media) and the
Interpret Footage fields in ui_drag_drop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Image sequences were found only by the desktop media prober: one picked
numbered still brought its whole run, with no way to import just that
frame, picking every frame made one sequence item per frame, the browser
couldn't make sequences at all, and a gap in the numbering closed up
silently.
The engine now groups numbered stills itself (engine/src/sequence.rs):
one picked file brings its run from its folder, several picked files of
one run import as that range, a picked or dropped folder imports its
files with each run as a sequence, and Force Alphabetical Order takes
every image of that type in the folder by name. `file.import` takes
`sequence` (default on), `alphabetical` and `frameRate`; Replace Footage,
Reload, proxies and render outputs probe the same way. The media layer
probes one file; the browser lists its file table for the run and
registers every frame with its media pool.
File > Import > File... asks, like After Effects' "PNG Sequence"
checkbox, whether picked numbered stills import as a sequence, with
Force Alphabetical Order and the frame rate. Sequences are named
`shot_[0001-0250].png`.
Interpret Footage gains Missing Frames (Show Placeholder, colour bars as
in After Effects, the new default; Hold Previous Frame; Skip) and Start
Frame (the frame number at the footage's first frame: trims or pads the
head). Saved projects keep Skip, so their sequences play as before.
Form notes keep a readable width and the form fits its rows, so OK stays
in view. docs/footage.md explains importing footage and sequences.
Test: tests_sequence (engine: run, picked range, every frame, Sequence
off, folder, alphabetical, Interpret Footage Missing Frames / Start
Frame, Replace Footage and Reload), project sequence unit tests,
sequence_gaps_show_placeholders_hold_or_close_up (media),
import_dialog_offers_the_image_sequence_option and
dropped_frames_import_as_one_sequence_with_sequence_interpretation (UI).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Importing a glTF / OBJ model opened Interpret Footage: the probe
reports straight alpha and the file has no alpha label, so with
Settings ▸ Import ▸ Interpret Unlabeled Alpha As at Ask User (the
default) the model was listed as unlabeled. Only image footage
(stills, sequences, video) can have unlabeled alpha; a model renders
its own, and audio and data have none.
Test: importing_a_model_does_not_ask_about_alpha (engine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Files dropped on the window or picked in the Import dialog were probed
one after the other on the UI thread: the app froze while a video was
read, nothing said what was being imported or how many files were
left, and only the last new item was selected. People ended up
importing the same file again and again.
file.import takes `background`: the files are probed in a background
job (Window > Progress, jobs.list / jobs.cancel) that names the file
being read and how many there are ("b.png (2 of 3)"); then they are
added in one undo step, all selected (the Project panel scrolls to
them, a file already in the project arrives again as in After
Effects) and a toast reports the count ("Imported 3 items", with the
first error). Drops and the Import dialog use it, and an Importing
card at the bottom left shows the job with its progress bar and
Cancel. Agents and scripts still import at once by default. The
toast registers an automation id.
Test: background_imports_report_progress_and_select_what_arrived
(engine), dropped_files_show_import_progress_then_the_items_they_made
(ui_drag_drop).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get_project / render.backend reported "gpuAcceleration": true with
"active": "cpu" and no reason, which read as a broken GPU. The
headless MCP server only attaches the GPU compositor when started with
--gpu, which docs/agents.md didn't mention, and a failed --gpu said
only "no usable GPU adapter" (the adapter's error went to a log no
one sees). The session now keeps the host's reason there is no GPU
compositor (accel_note): the CLI's "headless: renders on the CPU
unless started with --gpu", or the desktop app's compositor setup or
retirement error. The status reports it as `why` whenever it renders
on the CPU (or that the project's renderer is Software Only). --gpu
failures include the adapter's error (Gpu::try_headless).
docs/agents.md documents `mcp --gpu`.
Test: render_backend_says_why_it_renders_on_the_cpu (engine).
Checked with effectcraft-cli mcp and mcp --gpu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Composition viewer ignored drags from other panels. Now, as in After
Effects:
- A Project item dropped on the viewer becomes a layer centred where it
lands (above the selected layer, like any new layer); while dragging, the
viewer is outlined and the layer's frame follows the pointer.
- Media Browser files do the same; files dropped from the OS file manager
do too when the platform reports where the pointer is during the drag
(otherwise they import as before).
- An effect from Effects & Presets goes on the layer under the pointer,
which is outlined while dragging.
layer.addItem and layer.newModel take `position` ([x, y] comp px).
file.import takes `addToComp` with `time`, `index` and `position`;
mediaBrowser.import shares it (file::add_to_comp): layered files go in as
their comps, the comp they were dropped in stays open, and items that
can't be layers are reported in `errors`. viewer::layer_at is the hit
test pick() and the drop share.
Tests: ui_drag_drop.rs (project item and effect dropped on the viewer)
and imported_files_go_into_the_comp_where_they_were_dropped (engine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `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>
- 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>
- footage.check: background footage check after File > Open (Progress panel), not an undo step
- timeline: track-matte / parent menus built only while open (was O(n^2) per frame);
expression-row lookup per layer; panels hold Arc<Comp> snapshots instead of deep clones
- Project panel: virtualised vertical scrolling, selection reveal, thumbnails rendered off
the UI thread
- effectcraft-cli bench --ops (engine::perf + ui-egui::bench)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nonAffineTransform and the warp descriptor (named styles with bend / distortion, custom
envelope quilt meshes via ObAr meshPoints) are baked as placed over the placed bounds
(SourceLayer::placed), at up to 4x resolution.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text with embedded TrueType, CFF (Type1C / CIDFontType0C / OpenType), Type 1 and Type 3 fonts,
standard-14 fallback to the bundled OFL fonts; Flate / DCT / inline / stencil images with SMask,
Indexed and ICC alternates; luminosity / alpha soft masks and the 16 blend modes in the SVG
render tree; tiling patterns; calculator functions; file.import page and Footage::page;
Create Shapes from Vector Layer keeps clips (page clips as layer masks, nested clips as Merge
Paths Intersect).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Display Color Space setting joins the live list; View3D::Default maps to view.3d.default for
the Assign Shortcut submenu; stub tests use the stubs that remain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reconciled with main's keyframe labels (same Keyframe::label field; keys.setLabel and
keys.selectLabelGroup from main are kept, M14.5's duplicates removed; Edit ▸ Label on
selected keys delegates to the label helper), proxies (RenderOpts::proxy next to
nested_switches/draft_shadows), offloaded renders (Work carries nested_switches), wasm
audio pump (preview mix rate), variable font axes and mask feather points.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 3D Camera Tracker analysis joins the offloaded jobs. The disk cache stays off in the
browser (std::env::temp_dir panics on wasm32 once a config store exists).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Persistence: settings, shortcuts, recent projects, imported media, saved projects and
auto-saves live in OPFS (IndexedDB fallback) through an in-memory mirror flushed in the
background (apps/effectcraft-web/src/store.rs, persist.rs); ConfigStore gains FileOps so
auto-save / recovery work in the browser store; the session snapshot restores the last
project + editor state on reload; File > Open Recent works from storage.
- Audio: Web Audio output (AudioWorklet, ScriptProcessor fallback) fed by AudioPlayback::pump
on wasm32; A/V sync from AudioContext.currentTime; meters; unlock on first gesture.
- Background jobs: engine::offload (WorkerRequest/WorkerReply protocol, Offload trait,
run_request); Render Queue, tracker, mask tracker, Warp Stabilizer and Roto Freeze run in Web
Workers (a second engine instance on the shared compiled module), progress streamed back;
cancel terminates the worker.
- GPU: WebGPU viewer frames drawn straight from the compositor's texture (gpu_display on
wasm32 when the viewer draws plainly); async readback (Gpu::read_display_async) for pixels.
- PWA: manifest, service worker precaching the build (xtask fills list/version), icons copied
from assets/app-icon; COOP/COEP kept from cached responses; offline reload works.
- window.effectcraft: saveToBrowser, listStored, removeStored, flush; info() reports storage,
audio, gpu, workers.
- Tests: native store + protocol + offload (render, track, cancel) tests; smoke.mjs extended
(GPU path, worker render responsiveness, audio gesture + playback, reload persistence,
Open Recent, offline reload).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sampleImage renders the sampled layer through the evaluating renderer's footage source
(EvalCtx::footage), cached per thread and frame. File > Import reads .json/.csv/.tsv as data
footage kept in the project. expr.errors collects failing expressions for the error bar;
expr.languageMenu lists the categorised Expression Language menu. Marker keys expose
protectedRegion and cue points; colorDepth, timeToFeetAndFrames, comp.activeCamera.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comps expose controls (properties typed as text/color/slider/checkbox/point/angle/dropdown,
Media Replacement, groups, comments); precomp layers get an Essential Properties group whose
edits override per instance (Push to Comp / Revert); templates export/import as an open ZIP
(manifest.json + project subset + media + poster). Stretched precomps keep protected regions
at their original speed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New crate effectcraft-model (L2): glTF 2.0 (.gltf/.glb, data URIs, external buffers and
images, PBR metallic-roughness with base/MR/normal/occlusion/emissive textures, node
hierarchy, skins, step/linear/cubic animations) and OBJ/MTL import, parametric primitives,
extruded/bevelled outline meshes and polygon triangulation with holes.
Project: FootageKind::Model, LayerSource::Model/Primitive, Layer::environment, Geometry and
Material Options builders, Environment light Source. Media probes and loads models
(FootageSource::model). Engine: layer.newModel, layer.new3dPrimitive/newPrimitive,
comp.renderer, layer.environment, material.set/reset/duplicateAssign/revealSource (stubs
replaced); File > Import and layer.addItem pick up models. Hand-written fixtures with
attribution sidecars.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New L3 crate effectcraft-gpu: a wgpu compute backend for the per-frame
compositing walk (layer transforms with the CPU's nearest/bilinear/bicubic
sampling and minification pre-filter, motion-blur accumulation, track
mattes, Preserve Transparency, layer style passes and knockout, all 38
blend modes as a WGSL port of color::blend, 8/16 bpc clamp+quantise,
colour-space conversions). 3D runs, adjustment and wireframe layers run on
the CPU between GPU steps. GPU effects with the CPU semantics: Gaussian,
Fast Box and Directional Blur, Glow, Levels, Curves, Hue/Saturation, Tint,
Fill, Gradient Ramp, Fractal Noise, Drop Shadow, Brightness & Contrast,
Exposure, Invert, Transform (registry gpu badge from effects::GPU_EFFECTS).
render: Backend (Cpu/Gpu/Auto) in RenderOpts, the Accelerator trait and
Renderer::accel, GPU effect chains in the effect stack, hooks for the GPU
walk. Project setting gpu_acceleration (Mercury GPU Acceleration / Software
Only), render.backend command, file.projectSettings renderer. Session and
Render Queue exports carry the accelerator. CLI --gpu and bench --gpu.
Tests compare GPU vs CPU renders (blend modes x bit depths, transforms,
masks, mattes, motion blur, colour management, styles, collapse,
adjustment fallback, every GPU effect) and skip without an adapter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8/16 bpc projects clamp and quantise layer pixels after source/masks and
every effect, and the comp after every layer (16 bpc = 0..32768); 32 bpc
keeps over-range values. Blend modes without HDR support clamp their inputs
in 32 bpc. Working space (sRGB, Rec. 709, Rec. 2020, Display P3; matrices
derived from the published primaries), footage colour profiles from stream
metadata or Interpret Footage, Linearize Working Space and Blend Colors Using
1.0 Gamma. Layer cache keys include the colour settings. Project panel depth
button: click cycles, Alt-click opens Project Settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PRJ-7. `engine::autosave` with the session hooks the frontends call:
- Auto-save: every N minutes a dirty project is written to "EffectCraft Auto-Save" next to
it (or a custom folder; untitled projects use the custom folder or the config dir) as
"<name> auto-save N.ecproj", rotating through max versions (oldest overwritten, extra
slots dropped when the maximum shrinks). Optional save when the render queue starts.
- Atomic writes for projects, auto-saves and config files: temp file + fsync + rename; a
failed write removes the temp file and leaves the previous file intact.
- Crash recovery: a session.lock sentinel in the config dir records the open project and
its latest auto-save; a clean exit removes it. At launch a leftover sentinel offers the
latest auto-save in a Recover Project dialog (Settings > Startup can turn it off).
- File > Open Recent (stored in the settings) with Clear Recent Projects; Increment and
Save uses After Effects naming ("Intro 2.ecproj"); Revert reloads the saved file.
- Commands: file.autoSave, file.recoveryInfo, file.openRecent {index|path},
file.clearRecent.
- Tests: rotation, interval/dirty tick, simulated crash mid-write, unclean-exit detection
and recovery, Open Recent / Increment and Save / Revert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A real Settings model in the engine (`engine::prefs`): serde, versioned, dotted keys
(`general.undoLevels`, `labels.3.name`), migration of older layouts, unknown keys kept,
persisted through a `ConfigStore` trait (platform config dir on the desktop; MemoryConfig
for tests; the web app can back it with localStorage).
- After Effects 2026 pages (General … Scripting & Expressions) as a schema shared by the
dialog, `prefs.pages` and docs/preferences.md (rows not wired yet are listed there and
kept in step by a test).
- Commands: prefs.get, prefs.set {key, value | values}, prefs.reset {page?}, prefs.open
{page?}, prefs.pages.
- Wired settings: undo levels, path point size, tool tips, layers at comp start / CTI,
default spatial interpolation linear, Home screen on launch, new project template,
rendering progress in Info, adaptive resolution limit, cache when idle, fast previews,
theme + UI brightness, label names/colours everywhere (Label menu, timeline, project,
render queue, viewer), label colour for handles, grid colour/spacing/subdivisions, guide
colour, safe margins, still footage duration, sequence fps, audio output device and
output mapping, layer / footage / preview cache budgets, default 3D renderer.
- Settings dialog: AE's left page list + right content, OK / Cancel (restores) / Previous /
Next, every widget with an automation id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>