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>
Job workers remembered the files they were sent by path and byte
length, so footage replaced by different bytes of the same length at
the same path was never resent: a Render Queue export through a reused
worker rendered the old image while the page showed the new one.
Frame workers remembered paths only, so they never got a replacement
at all.
Every write of a stored file now gets a version, and both kinds of
worker remember the version they were sent.
Tests: versions_change_with_every_write (store) and
apps/effectcraft-web/tests/workers.mjs (Node, fake Worker: the
replacement is sent once, an unchanged file not again).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018gnTnH3Jmk5zr4tMq1Mc9f
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>
- 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>
- 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>
- apps/effectcraft-web (L6): eframe web runner booting the shared EffectcraftApp with a browser
session: in-memory file table, <input type=file> pickers and drag-and-drop for open/import,
downloads for Save and Render Queue outputs (several files as one stored .zip), and the
window.effectcraft promise API over the control channel. Demo project on start.
- wasm32 runtime fixes: web-time for Instant/SystemTime (render, engine, export, ui-egui);
Frames renders queued jobs on the UI thread (Frames::pump) instead of a thread pool;
Render Queue runs inline; relative outputs resolve under / ; control PNGs inline.
- effectcraft-export: per-job output Sink (files to memory instead of disk); host FileExporter
carries it; test that sink exports touch no file.
- xtask web [--dev] [--serve PORT] (wasm-bindgen 0.2.129, optional wasm-opt, COOP/COEP server);
effectcraft-web in WEB_CRATES. docs/web.md, headless Chrome smoke test, favicon (+attribution).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>