Problem: After Effects 26.3's font menu can show only variable fonts.
EffectCraft's font menus (Character panel, Properties ▸ Text) had no such
filter, and text.fonts / MCP list_fonts didn't say which families are
variable.
Fix: the font scan marks a face variable when its table directory has an
fvar table declaring axes (header read only, no extra file loading). The
font menu gets a "Variable fonts only" checkbox above the results; it
combines with the typed search, keeps the keyboard in the search field and
stays set while the app runs. text.fonts reports `variable` per family and
takes `{variable: true}`; list_fonts passes it through. Catalog rows for
ja, zh-Hans, zh-Hant and uk.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`effectcraft-cli mcp` had no way to keep an agent's file access to a folder;
the flags were unknown options. Both now take a folder (repeatable, as in
PhotoCraft): with either given, files are read only under a read or write root
and written only under a write root (none: no writes). Defaults are unchanged.
automation::confine::Confinement checks every engine command the backend runs
(execute_command, command_run, open/save_project, each engine.batch step), in
both headless and bridge mode, before it runs: the file parameters of the
commands that read or write files must be absolute paths inside the roots
(symbolic links resolved through the longest existing ancestor, `..` refused),
as must any other absolute path in a command's parameters. Commands that write
where they aren't told are checked against the target they would write: Save /
Increment and Save the open project's file, renderQueue.render every queued
output (renderQueue.list). Scripts, Execute File, Reveal in Finder, Open Recent
by index, `$N` file-path references in a batch, render_frame/screenshot paths
outside a write root and the bridge's UI-driving tools are refused while
confined. docs/mcp.md describes it.
Test: a confined headless server opens and saves inside its roots and is refused
outside them, for a relative path, `..`, a batch step, a `$N` path, Save to a
read-only root's project, the Render Queue's default output there, a symbolic
link out of the write root, and scripts; the unconfined server still writes
anywhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
- file.relinkFootage (#461): close the params doc so checked execution
(MCP, control channel) rejects unknown keys; translate its menu entry.
- general.language lists Português (Brasil) (#217).
- docs/agents.md: set_property / add_keyframe take timeBase (#522).
- #299's MCP test no longer sends get_property a value.
- The Automatically Reload Footage sequence test uses PNG frames.
- h264_frames_at_reduced_sizes (#507): the bar depends on the fixture's
encoder; 50 dB with ffmpeg 6.1's x264.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
- 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]>
Follow-ups from the #365 review:
- serve() no longer hangs when a reply cannot be written: the stdin reader thread is
detached instead of scoped, so a failed write returns at once rather than waiting for a
read that only ends at EOF. serve() now takes a 'static reader (serve_stdio is unchanged).
- A test pins down requests during a render: a second render is refused however it is
started (blocking, wait: false, inside a batch), and edits apply to the project while the
job keeps rendering its snapshot.
- docs/mcp.md: isError is true for any JSON result with failed > 0; what requests mean
mid-render; wait: false renders are polled with renderQueue.list.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Text is vector in After Effects and stays sharp at any Scale. Here a
text layer was drawn at its font size and the bitmap resampled by Scale
unless Continuously Rasterize was on, so text grown with Scale went soft.
This reworks #200 (reverted in 29c4d3a for kinetic_typography).
Why kinetic_typography failed: its time remap key at 1.9 s sits on
frame 23 (1.917 s), so main-comp 0.3 s shows the precomp at 1.6026 s,
but the test compared it with the precomp rendered at 1.6 s. The moving,
motion-blurred title already differed by 0.89 of the 1.0 mean-difference
budget on main (0.987 in all); with continuous rasterisation the "Sub"
line, whose expression scale is 84.00 % at 1.6 s and 84.105 % at
1.6026 s, falls on either side of the 2^-1/4 quarter-octave step and was
drawn at 0.84 at one time and at 1.0 at the other, pushing the total to
1.075.
Fix: raster_scale treats text layers as always continuously rasterised
(shape layers and vector footage still follow the switch), and rounds
the quarter-octave step up past rounding noise only: an unscaled layer
at a fractional position measured its scale as 1 + 1e-15 and was
rasterised at 2^1/4, softening unscaled text. The QA test now compares
the remapped precomp with the precomp at the time the remap really
shows (read back from the property): difference 0.63, all of it the
title's motion blur, the Sub line identical.
Tests: tests_collapse::text_scaled_up_stays_sharp_without_the_switch
(from #200: 10 px text at 800 % matches 80 px text),
tests_collapse::unscaled_text_rasterises_at_the_output_scale (fails
without the noise tolerance), automation qa kinetic_typography.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#365 rejects arguments missing from a tool's schema; both tools read `id`
as an alias of `command`, so declare it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use an explicit FMA for the final quotient correction at Posterize
boundaries. Align the kinetic motion-blur QA comparison with export
settings and credit sanyamsingh001.
Co-authored-by: Codex <[email protected]>
Keep serving during long renders, recover failed workers, and remove only files opened by the interrupted export. Cover progress, cancellation, EOF and output ownership with regression tests.
Co-Authored-By: OpenAI Codex <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
add_effect ran effect.apply and then one prop.set per `values` entry as
separate commands: each became its own undo step, and a bad value (an
unknown parameter id) returned an error but left the effect, and any
values set before it, on the layer, so a corrected retry landed at
effects/#2.
It now runs them as one engine.batch, named like effect.apply's own undo
step ("Apply Gaussian Blur"): one undo removes the effect with its values,
and a failing step rolls the whole call back. The reply is unchanged.
Test: add_effect_is_one_undo_step_and_all_or_nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
prop.setExpression keeps an expression with a syntax error but disables
it, and says why only in its own reply. MCP set_property answers with
prop.get instead, and prop.get reported `evaluated` / `expressionError`
only for enabled expressions, so set_property, get_property and prop.get
all showed the broken expression as if it were installed.
prop.get now reports a disabled expression too: `evaluated` is the
static value that renders, and `expressionError` is its syntax error.
Test: expression_syntax_errors_reach_the_reply (set_property and
get_property carry the SyntaxError; runtime errors and valid expressions
report as before).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With installed families whose names contain the query (JetBrainsMono
Nerd Font and its variants) the test expected exactly one match and
failed. It now checks that every match contains the query, the count
agrees, the query filters, and the bundled JetBrains Mono is there with
its Regular style.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Tools table gains history and script_ui, get_layer's depth,
add_keyframe's comp and the bridge screenshot's max_side and path.
agents_doc_tools_table_is_current (effectcraft-automation) fails when a
tool or an argument of its input schema is missing from the table, as
effects_doc_is_current guards docs/effects.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Windows user reported that the Properties panel's font menu offered only the bundled Inter,
JetBrains Mono and Noto Serif. The system font folders were scanned only when a project asked for
a family the bundled fonts lack, so until then every font menu (Properties, Character, Essential
Graphics) and `file.findMissing {what: fonts}` saw the bundled families alone, on every
platform. Listing the families now scans the system fonts first, and the desktop app starts that
scan on a background thread at launch so the menus are complete when first opened.
The style menus in the Properties and Character panels list the chosen family's own styles
instead of a fixed Regular / Medium / SemiBold / Bold / Italic. Both menus build their lists only
while open.
New query for agents: `text.fonts {query?, rescan?}` (MCP tool `list_fonts`) lists every family
with its styles, origin (bundled / system / user) and own-language name; `rescan` picks up fonts
installed since launch.
Tests: a fresh-process test that the family list includes every family in the platform's font
folders without a prior scan (failed before), and `text.fonts` filtering, rescan and per-family
styles, and the `list_fonts` MCP tool.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019EjTYdt76hTG4Kh2CJFnCr
- effectcraft-segment: a FaceModel interface (find a face in a region, follow it; a Topology
maps the model's points to the tracker's landmarks and outline), a TensorFlow Lite reader and
interpreter (float ops, float16 weights folded; matches ai-edge-litert to ~1e-6), and
MediaPipe Face Landmarker (Google, Apache-2.0: BlazeFace + Face Mesh V2, 3.8 MB, pinned
SHA-256). The registry gains a task and authors per model; installed weights get a NOTICE.
- effectcraft-track: FaceTracker::new_with uses the model, falls back to the classical fitter
when it finds no face; chin and jaw come from the outline either way so measurements agree.
- Engine: roto_models becomes models (both tasks); face.models / face.model.* commands,
Settings ▸ Face Tracking, `wait` on select/install/download for scripts; track.mask reports
faceModel.
- Credits: ATTRIBUTION.md lists the optional models and the Apache-2.0 specs the code follows.
- Docs: README, ROADMAP (G7), gaps, parity (TRK-3 score unchanged pending G1), agents,
preferences, architecture, segment README.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- New L1 crate effectcraft-segment: the MaskModel interface (prompts → foreground probability),
a registry of open-licensed models (licence must be MIT/Apache/BSD/…; URL, size, SHA-256),
a PyTorch checkpoint reader (zip + pickle subset), dependency-free SHA-256, and MobileSAM
(TinyViT-5M + SAM prompt encoder / two-way transformer / mask decoder) on rayon and ndarray's
safe GEMM. Loads the official mobile_sam.pt as published; checked against candle
(embedding 0.3% RMS, 99.9% equal mask pixels); ~0.7 s/frame encoder at 32 threads, cached
per frame, ~0.1 s decoder.
- track::roto: segment_with / propagate_with prompt a model with the strokes or the flow-warped
matte (box, core points, mask); its probability is a strong, unblurred prior for the same
graph cut (strokes stay hard constraints); a failing model or one disagreeing with the flow
(IoU < 0.5) is ignored. Prior gets a weight.
- effects::roto: Version 2.0 / 3.0 use the model chosen in Settings (set_model / model_for);
the model id is part of the chain seed.
- engine: roto_models (roto.models, roto.model.select / install / download / remove), weights in
the models folder (host::config_dir()/models, shared by the app, CLI and MCP), background
loading, downloads through the system curl, verified; Settings ▸ Roto Brush page (prefs
roto.model). host::config_dir moved from the desktop app.
- Tests: segment (kernels vs direct sums, checkpoint reader, SHA-256 vectors, registry), track
(fake model: prior shapes the cut, failing / disagreeing models ignored; real weights with
EFFECTCRAFT_MOBILESAM: base IoU 0.989, propagated ≥ 0.980 vs classic 0.973), engine (registry,
verification, selection; real install + load), MCP.
- Docs: README, ROADMAP, parity (MSK-4 done: 90/2/0), preferences, agents, architecture,
crates/segment/README.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
These crates have no production panic sites: add the deny attribute so they stay clean
(geom, color, model, automation, host, export, plugin, and the desktop, CLI and web apps). The
plugin's wasm-only test module gets a separate cfg(test) attribute so clippy recognises it as
test code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bump the FilmCraft crates from 89cfbf2 to 2c5ba7a (118 commits; no API
changes needed here), whose Matroska reader now decodes the VP9 alpha
stream from BlockAdditions. The compositing QA scenario now checks that
WebM renders round-trip their alpha like ProRes 4444.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- command.describe / list_commands {schemas} give each command's params JSON Schema;
MCP describe_command, add_effect, list_effects and get_state (app.capabilities)
- effectcraft-cli exec --list [--schemas]
- headless UI audit: every panel kind's interactive egui widgets are covered by
automation ids (fixed: timeline navigator, label swatch, bar trim edges, group and
property name cells)
- command lookup is indexed
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- ui-egui: scriptui_view draws script windows at engine layout bounds; PanelKind::ScriptPanel; File ▸ Scripts and Window menu list scripts/panels (egui + native menus)
- examples/plugins/posterize-bands (WebAssembly, API v1); host plug-in tests; desktop loads <config>/Plug-ins
- MCP: script_ui and history tools; docs/plugins.md, agents.md, parity, architecture, ATTRIBUTION
Co-Authored-By: Claude Opus 5.5 <[email protected]>