Add Layer Styles submenu to the Timeline and Composition viewer layer context menu, matching After Effects parity and the application Layer ▸ Layer Styles menu.
- Adds Layer Styles submenu between Blending Mode and Effects
- Exposes Layer Style Options..., Convert to Editable Styles, Show All, Remove All, and the 9 individual styles
- Translates Layer Styles across all 7 UI catalogs
- Adds regression test verifying submenu presence, position, and action invocation
Load PDF document objects through the pinned craft-pdf parser and convert them into EffectCraft's existing drawing model with bounded traversal, recovery and retained data. Preserve generation numbers, stream limits, encrypted-document refusal and reported repairs.
Retain app-owned drawing, EPS, fonts and content/image decoding. Assert early excessive-nesting rejection without partial art, and cover tolerant arrays on native and WASM.
Validated with the22-stage local matrix and exact-head FreeBSD, Windows ARM64 and packaging checks.
Co-authored-by: Codex <noreply@openai.com>
In the browser editor a .jsx script couldn't open any file, even one the
page holds next to the project: File.open("r") returned false with "File
not found" and File.exists was false. The footage check after File > Open
also marked imported media as missing.
Both asked whether a file exists on the local disk (std::fs), which has no
files on wasm32:
- scripts read files through Session.services but checked existence with
std::fs (File.open's access check, File.exists, Folder.exists); they now
ask services.exists / services.is_dir, after the folder check;
- WebServices kept the default exists (std::fs); it now looks in the file
table.
Desktop and CLI are unchanged (FsServices keeps the std::fs defaults).
Tests: script tests_services (a host with in-memory files), web smoke
step hostFiles (a script reads a stored file, footage check finds stored
media).
Retry only explicitly read-only control queries after connection failures. Report ambiguous edit outcomes without resending mutations, reconnect on the next explicit call, and reject request-ID exhaustion without overflow.
Add loopback regressions for lost and malformed replies, read-only retries, playback queries versus edits, and unknown methods.
Fixes#1247
Problem: effect colour labels (26.5) were drawn only on the Effect Controls header; the Timeline's effect rows looked unlabelled.
Fix: an effect group row in the Timeline paints the label colour as a 4 px bar on its left edge (same look as the Effect Controls header) and registers `timeline.group.<uid>.label` with the label's name for agents. Docs: gaps.md row 21 and ui-parity.md updated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Follow up on #1210: remove the fixed menu index from automation and click handling, and move the preset-folder documentation into the Effects & Presets section.
In After Effects solo is not time-dependent: while any AV layer is soloed,
non-soloed layers are excluded at every time. The renderer, nested/collapsed
3D paths, engine viewer helpers and viewer hit tests only counted solo layers
active at the current time, so outside the soloed layer's in-out range the
whole comp reappeared. Drop the time condition (audio already ignored it).
Update the #66 viewer regression test to the AE behaviour and add a render
test for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every layer gets the Video switch (eye) in the Timeline (nulls, cameras and lights
were excluded by an is_av() check). A selected layer whose eye is off no longer
draws its box, handles, anchor or paths in the viewer. Alt-click on an eye and
Layer > Switches > Hide Other Video now take nulls along.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects' Liquify shows Distortion ▸ Offset (x, y), and the renderer
already applied distortionMeshOffset — sampling the mesh at layer
coordinates minus the offset — but the parameter was declared hidden, so
users could neither see nor edit it, and #1194 read as 'the effect does
not work and the Offset option is missing'.
It is now a visible 2D Point control (still defaulting to 0,0, still
scriptable by its old path). Regression test: the spec exposes it as a
Point, a [20,0] offset moves the displaced-region centroid right by the
offset (the render differs, and the left of the shifted mesh is back to
untouched pixels), docs/effects.md refreshed via UPDATE_DOCS.
The Japanese data point in #821 (zitann2020) offered to write its rows against whatever mechanism
this PR lands, so both languages share one catalog instead of inventing two. This makes adding a
language a data change and reserves `ja`:
- `Catalog` holds a language's effect rows and its category labels, and `catalog()` resolves a
language code to one; adding a language is two tables and one arm, with no change to the lookups
or to any panel.
- `JAPANESE_EFFECTS` and `JAPANESE_CATEGORIES` are declared, keyed by the same effect ids and by
the registry's English category names, and `ja` resolves to them. Both are empty, so a Japanese
interface draws exactly what it drew before — the slot changes no behaviour on its own.
- `a_pending_language_only_uses_known_ids` guards the pending tables (Japanese and Traditional
Chinese) as rows arrive: an id Simplified Chinese does not know, a different English name for one
of them, an empty translation or a repeated id fails. `languages_without_rows_draw_the_registry_name`
asserts each pending language has an arm in `catalog()` (a table with no arm would leave a
translator's rows dead) and is still row-less, so the day one gets rows the test says where its
coverage moved.
- `only_the_brand_names_stay_in_english` now documents that it pins *Simplified Chinese's* five
brand names and is deliberately not shared: AE's Japanese interface keeps the whole `CC`, `VR`
and `OCIO` sets in English while its Chinese one translates them, so repeating the English name
is a language's choice and not an unfinished row. The row check reflects that (a repeated English
name is accepted, an empty one is not).
docs/localization-parity.md records the two empty tables and that each language decides for itself
which names stay in English.
Refs #821