The app bundle exported ai.storyteller.effectcraft.document with the
`.pcraft` extension, PhotoCraft's document format. Installing EffectCraft
took `.pcraft` over, and since EffectCraft declared no document types,
neither app was offered for PhotoCraft documents in Finder. EffectCraft's
own projects (.ecproj, .ecprojx) had no file type at all.
- Info.plist: the exported type now tags .ecproj and .ecprojx ("EffectCraft
Project", conforming to public.data and public.content rather than
public.image), and CFBundleDocumentTypes makes EffectCraft their owner.
- Finder double-click, Open With, Dock drops and `open -a EffectCraft
x.ecproj` send a kAEOpenDocuments Apple event that winit 0.30 doesn't
handle. Register it with the audited fmv-macos-events crate that
PhotoCraft and PdfCraft use (it keeps the unsafe Objective-C interop out
of this workspace). Opened paths go through the dropped-file handling, so
a project opens after the unsaved-changes prompt and media is imported. A
quit event closes the window.
- The event that launches the app arrives before the UI draws: the first
frames skip the panels until the fonts are installed, which dropped it.
The paths wait for EffectcraftApp::is_ready().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
effect.reset looked parameters up by their full spec id among the effect's direct
children, so every 'group/param' id (most of Fractal, Scribble, and other grouped
effects) was skipped and Reset did nothing for them. Fixes#526
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The degrees field shows the remainder's size, so a drag was applied to the size and
the sign was re-applied afterwards; past zero it flipped back to positive. A drag now
moves the angle itself. Fixes#569
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A PDF could abort the process through its colour spaces and functions.
An Indexed hival of 10^12 asked an image for a 24 TB palette. A sampled
function's outputs (one per /Range pair) multiplied its table without a
bound, and copies of the table (one per q, one per reference) multiplied
it again. Self-referencing colour spaces and functions overflowed the
stack, a calculator's copy doubled its operand stack, index, idiv and
mod could panic, and f64::clamp panicked on a reversed /Domain or
/Range. Clamp hival to 255, count samples with checked arithmetic (at
most 32 inputs and outputs, 2^24 samples a table), share the samples,
give one function read a budget of nodes and samples, limit nesting to
8 levels and the calculator stack to 100, and clamp with the bounds in
either order.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
- Wrap remaining hardcoded UI labels and buttons with tr()/tl!() calls
- Add missing Simplified and Traditional Chinese translations
- Add entries to other language catalogs for consistency
- automation / cli autosave restart tests: the MCP instructions embed the
auto-save state as JSON, so a Windows path's backslashes are escaped there;
look for the JSON form of the path.
- ui_gpu: egui_kittest prefers a CPU adapter, which on Windows is WARP on
DirectX 12, where FXC fails to compile the compositor's `pointwise` kernel
("compilation aborted unexpectedly"), so the viewer stayed on the CPU and
the test failed before comparing anything. Build the harness on the adapter
the headless compositor picked (falling back to egui_kittest's choice).
- ui_audio_scrub (#103 test): with a fixed 2 s comp, a machine with many
render threads cached the whole preview before the first step, so the sound
rightly started at once and no silent frame was counted (3 of 24 runs
failed with 8 copies in parallel). Size the comp to four times the frames
playback requests per step, so caching takes at least four steps (0 of 24).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reload Footage (Cmd+Alt+L) updated an item's metadata but left the media
pool's decoded frames and the layer cache in place, so the old image kept
rendering until Edit > Purge. It also replaced the footage with a probe of
the merged document, so a Photoshop layer item lost its layer and its
Retain Layer Sizes bounds.
Reload now re-reads only what the file decides (size, a movie's duration,
streams and frame rate, codec, a data file's text), keeps the layer or
page and the interpretation, finds a Photoshop layer again by name, and
makes the media layer forget the file (FootageSource::forget, which also
drops the EXR aux-channel cache) and clears the layer cache.
Settings > Import > Reload Footage Changed on Disk (on by default): the
desktop app stamps footage files (size + modification time) on a worker
thread every two seconds and when the window comes back to the front, and
reloads what changed. Not an undo step and it doesn't modify the project;
a file still being written is retried on the next check.
footage.reloadChanged runs one check (CLI, MCP, agents).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Those edits offset the combined Position property, which a separated layer does not render. X and Y Position, including their keys, now move by the same amount.
The renderer follows X, Y and Z Position once those channels exist. The Position command was writing only the combined property, so the layer stayed where it was.
Setting the start past the current end, or shortening the composition, stored an inverted interval. Trimming that interval then saved a negative duration.
Every pointer move replaces the comp's content identity, so the frame on
screen takes a new cache key at every step while its render is still in
flight; the viewer only ever showed an exact key, so the picture froze on
the last completed frame until the drag ended. While a drag or a property
scrub is running, show the newest frame of the same comp, frame, scale,
view and render options that is ready — a render behind, as After Effects
does — and go back to exact frames when it stops.
macOS's /var is a symlink to /private/var: the CLI resolves its working
directory through current_dir(), which is canonical, so the recorded
output path was /private/var/... while the test expected /var/....
Canonicalise the test's temp directory so the comparison holds on macOS.
The drag preview was a 1 px axis-aligned rectangle for every shape tool,
which read as no preview at all against most comps. Draw the tool's own
path instead (rectangle, rounded rectangle, ellipse, polygon, star), from
the engine's own geometry, filled with the Tools bar's Fill and outlined
in its Stroke, with a caption saying where the shape lands — a mask on
the selected layer, into the selected shape layer, or a new shape layer.
The ghost and the released shape go through one normalisation, so the
drag shows exactly what it commits: Alt (Option) draws from the press
point and Shift squares the box, both live. The drag also starts at the
press point now, not where the 6 px drag threshold was crossed, so the
shape is drawn from where the button went down, as in After Effects.
GIFs went through the still-image path, so an animated GIF came in as a
single frame with zero duration. A GIF with two or more frames is now
probed as a movie: its frame rate is chosen so every per-frame delay is
a whole number of frames (uneven timing is kept exactly), delays of 0
or 1 hundredths count as 10 like in browsers, and the pool decodes the
frame shown at the requested time. A single-frame GIF stays a still.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
decode_stream_within now bounds the output of each stage of a /Filter
chain, not only FlateDecode. RunLength, LZW, ASCIIHex and ASCII85 stop
when their output would pass the limit, a full LZW table takes no more
entries, PNG predictor rows are sized with checked arithmetic and only
allocated when a whole row fits in the data, and CCITT checks
/Rows x row bytes against the limit before allocating.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
file.uninstallScript and script_code joined the name from the request into
Scripts/<name> and handed it to the settings store, so "../.." reached files
outside the settings folder (uninstall deleted them; run-by-name ran them).
Accept only bare file names (and, on Windows, none of its reserved names),
require a .jsx/.js name, and skip refused names in the script list.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
inflate used the unbounded miniz_oxide functions, and File::parse decodes
every object stream eagerly. Use the limit variants (1 GiB per stream, 256 MiB
for object streams, a quarter of that on 32-bit targets) and bound the
truncated-stream salvage buffer too.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The second pass over the interface below the menu bar: the strings that are technical terms or
proper nouns - `Mercury GPU Acceleration (…)`, `8/16/32 bits per channel`, `OCIO (built-in ACES
config)`, `sRGB IEC61966-2.1 (default)`, `Import Photoshop File`, `Current Settings`, the viewer's
channel and Fast Previews items. Each wording was checked against the installed After Effects
26.5.0 Simplified Chinese, the way the menu bar was in #353. Terms Adobe keeps in English
(`Display P3`, `HDTV (Rec. 709)`, `RGB`, `Alpha`, `H.264`, `ProRes`) need no row: a missing row
falls back to the English source, which is the decision.
The text reaches the catalog through the funnels that draw it, so one change each covers every
call site:
- `forms.rs`: field labels, notes, choice items and info bodies, plus `dialogs::modal`'s title -
the modal id stays keyed on the untranslated source, so changing the language cannot move it;
- `settings.rs`: page titles, sections, notes, buttons, setting labels and choice items (the page
table itself is engine data and cannot reach the catalog);
- `comp_settings.rs` (3D renderer), `key_dialogs.rs::combo` (Current Settings and the roving
items), `properties.rs` + `timeline.rs` (the text animator menu), `dialogs.rs` (About links);
- `render_queue.rs`: the Opus bitrate goes through `tr_args`, so the value is substituted after
the lookup - worth calling out, because building the string with `format!` and translating the
result looks up the assembled text and silently stays English (the same trap as the project
settings' GPU label, which this commit also fixes).
30 rows join the Simplified catalog. The same keys join the Traditional one with the English
source as their value: the tests require the two lists to match, and the Traditional wording is
the translator's call.
Tests: `CONVERTED` gains the three engine files whose strings the interface draws
(`prefs.rs`, `viewer.rs`, `commands/viewer_cmds.rs`); each holds no drawn literals, so the literal
scan stays green and the new rows have a source to be "used" from. `cargo test -p
effectcraft-ui-egui --lib i18n`: 16 passed.