Macro Recorder records the edits you make in Creator — transform changes, fills, keyframes, structure, and more — as a named, replayable macro. It replays those steps onto whatever you select.
It is published in the Creator Extensions marketplace: extensions.lottiefiles.com/plugin/inerds/macro-recorder. Install it from there to use it; read on to build it, run it without Creator, or use it as a reference.
The user guide is published on the
project wiki, a
read-only mirror of docs/user-guide.md that a push to main regenerates. The
other documents stay in docs/.
This repository is also an open-source reference for Creator plugin developers. Beyond the product, it documents patterns that any Creator plugin needs:
- A panel-to-sandbox RPC protocol with a mock fallback, so every panel state
is reachable without Creator (
ui/gateways/,engine/protocol.ts). - The QuickJS sandbox constraints that the typings do not mention, above all
the no-job-pump callback contract (
pnpm test:quickjsenforces it). - The host API's real runtime surface, found by introspection
(
docs/runtime-api.md), and confirmed host limits with evidence (docs/limitations.md). - A fake host scene for tests and a browser harness that reproduce the real
proxies' traps (
engine/testing/fakeScene.ts,dev/harness/host-harness.html). - Trace-driven debugging: every dev session writes an auditable bundle of RPC traffic, snapshots, and probes (see "Diagnostics and triage").
Inside Creator, the real engine runs. The panel polls the plugin sandbox
every 500ms over a small RPC protocol. The sandbox snapshots the scene, diffs
it against the previous snapshot, and returns human-labeled steps. Macros
persist in creator.clientStorage. Standalone, in a plain browser tab, the
handshake times out and the panel falls back to mock gateways and the dev
strip.
Document map:
docs/user-guide.md— the end-user walkthrough of every feature.docs/architecture.md— the invariants behind the code's shape: the three TypeScript projects, the sandbox constraints, the proxy boundary, the recording engine, and the gateway seam.docs/design-system.md— the panel's skin, deck, and rack rules.docs/runtime-api.md— the host API's real runtime surface. Read it before you extend the engine; the published typings still diverge from the runtime in the places that file lists.docs/limitations.md— confirmed host limits, with evidence.CONTRIBUTING.md— setup, the checks a change must pass, and how a release is cut.docs/contributing/— the writing style standard, the trace-triage workflow, and theENGINE_REVrule.docs/history/— the engineering log, the 2026-08-24 UI audit, and the v3.1 roadmap.docs/releases/— the release notes per shared build.CHANGELOG.md— user-visible changes per version.
engine/holds the pure, fully unit-tested logic both sides use: protocol, snapshot model, structural differ, labels, relative-playback math, simplification, and value editing.sandbox/is the QuickJS sandbox: RPC dispatcher, defensive proxy-to-snapshot serializer, step applier, and theclientStoragestore. The sandbox has no timers — the panel owns all timing.ui/gateways/rpc/is the panel side: the RPC bridge, the tick-loop recorder gateway, and the paced step-by-step playback orchestrator.
What you select before you press Record decides what the recorder watches. Select one or more layers, and it records those layers only: their subtrees, paints, masks, trims, plain flags, and structure. A selected shape counts as its layer. Layers that appear while you record, such as duplicates and new layers, join the recording. Select nothing, and it records the whole scene, the scene settings included. The deck shows which one Record will do. An edit outside the recorded layers is dropped and counted on the recording screen, never dropped in silence. While you record, selecting one keyframed recorded layer offers to capture its keyframes and current style into the macro. Hold Option (macOS) or Alt (Windows) while you press Record, and the recording stores every layer-transform step as the value it ended at rather than as a delta.
Replay picks one of two modes. A macro that touched at most one layer applies
to every selected layer: the layer's own position, rotation, and skew
shift each target from its own start, scale multiplies, and everything else
applies exactly. Each transform step carries a formula on the target's
current value, and the pencil opens it as a verb and a number — Set to, Add,
Subtract, Multiply, Divide, or Formula… for a whole expression such as
v * 2 + 10, where v is that current value. Only layers are targets — a
selected shape is dropped with a note, because every step addresses its layer
by path. A macro that touched several layers, or that restructured the scene,
replays as a scene rebuild: each step finds its layer by recorded id,
then by name, then skips with a note.
Two rules govern every step:
- Keyframes converge. A macro means "end up like this". Frame numbers are the only keyframe identity, adds upsert onto occupied frames, and removes of absent keyframes do nothing.
- Nothing applies silently. The host discards some writes without an error, so the applier verifies and reports a note for every non-apply or adaptation. Genuine failures pause for Continue or Stop.
Pro tools are client-side data transforms: Simplify, per-step disable and edit, parameters (pinned values asked for on play), and the play options — at playhead, stagger, and repeat ×N.
Install Macro Recorder from the Creator Extensions marketplace and open it from Creator's plugins menu. That is the released build; the two ways below are for developing it.
You need Node.js 22.12 or later and pnpm (the repository pins pnpm@10.33.0
through packageManager).
pnpm install
pnpm devOpen http://localhost:5173 and size the viewport to about 320×560, the
size the plugin asks Creator for in sandbox/plugin.ts. The
Dev settings strip at the panel foot (dev builds only) loads the ten demo
macros, clears the store, controls the mock recorder and playback scenarios,
and shows captured traces.
Run pnpm dev, then add the plugin in Creator:
- Open creator.lottiefiles.com.
- Open the Plugins panel.
- Click the + icon at the top right.
- Open the Develop tab.
- Enter
http://localhost:5173and click Continue.
After any change under sandbox/ or engine/, remove and re-add the
plugin. Creator evaluates plugin.js once and never re-fetches it, while
Vite serves the panel fresh. Bump ENGINE_REV in engine/protocol.ts with
every sandbox-side change; the handshake compares revisions and logs a loud
plugin engine is STALE warning on mismatch.
http://localhost:5173/host-harness.html emulates the host: a fake creator
global, fake scene nodes, the plugin bundle, and the real panel iframe. The
dev server compiles and serves plugin.js on request, so pnpm dev alone is
enough — there is no build step to remember. Drive the fake scene from the
console through window.harness. pnpm test:harness drives the same page
without you (see scripts/ui-probe/README.md).
dev/harness/sandbox-test.html reproduces Creator's opaque-origin iframe (no
localStorage, no crypto.randomUUID) for the fallback paths.
pnpm test # vitest: engine logic, reducer, demo and corpus replay (899 tests, 38 files)
pnpm test:quickjs # builds, then drives dist/plugin.js in real QuickJS
pnpm test:ui # opens the panel in headless Chrome and probes the DOM
pnpm test:harness # records and replays through the host harness in headless Chrome
pnpm type-check # tsc -b across all three project references
pnpm build # production bundle → dist/ (manifest.json, plugin.js, ui.html)test:quickjs exists because Creator invokes the sandbox's callback without
pumping the VM job queue. A pure VM promise chain never settles there, so code
that passes in a browser can be dead in Creator. The smoke test drives the
real bundle with zero pumps and asserts the RPC contract holds.
pnpm test also replays the macro corpus — one saved macro per shape this
plugin has ever written to storage or export — so a change to the step shape
that an older macro cannot survive fails a test instead of blanking a panel
(engine/testing/macros/, and see
docs/contributing/macro-corpus.md).
test:ui exists because the unit tests run in Node with no DOM. It opens the
panel in headless Chrome over the DevTools protocol and asks the page what
paints on top, what a box measures, and what a key does under a modifier —
the questions that caught a menu buried behind the deck.
test:harness exists because the other checks each see one half of the
plugin. It drives the host harness in the same headless Chrome: the real
sandbox bundle, the real panel, and the fake scene as the host. It records an
edit, saves the macro, replays it onto other layers, and asks the fake scene
what the values became. It is the only check that runs record and playback
together. Both suites share one driver — see
scripts/ui-probe/README.md.
sandbox/ The QuickJS plugin sandbox: RPC dispatcher, serializer, applier, store, manifest.
engine/ The pure engine both sides use: protocol, snapshots, differ, labels, simplify.
ui/ The React panel: state machine, gateways, components, styles, dev strip.
dev/harness/ Host-emulation pages for the dev server only. Never part of the build.
scripts/ The trace server, the QuickJS smoke test, the release bundler, and the
release-notes helper.
docs/ User guide, architecture, design system, runtime API, limitations,
contributing guides, release notes per version, and the history log.
.claude/ The triage agents, the /triage-traces skill, and the two installed
LottieFiles Creator plugin skills.
.github/ CI (type-check, tests, QuickJS smoke, build) and the tag-driven Release workflow.
Each tree compiles under its own tsconfig.*.json and tsconfig.json is the
solution file. pnpm bundle builds and writes release/macro-recorder-v<version>.zip
with exactly the three files Creator needs, and pnpm bundle:dev writes the
-dev build with the dev strip on, the name "Macro Recorder (dev)", and its
own plugin id, so the dev build keeps a separate macro store in Creator. The
build stamps the version into manifest.json.
Dev sessions write a trace bundle per record and playback run to
traces/. Each bundle carries what the panel never shows: every RPC call
with timing, the {prev, next} snapshot pair behind each recorded tick, and
per-target probes before and after each playback step. Snapshot and probe
payloads are opt-in per session (debug: true, dev builds only), so a
production panel gets byte-identical responses.
To triage captured traces, run the skill in Claude Code:
/triage-traces
The skill:
- Lists the trace files that
traces/.processeddoes not name yet. - Fans out one read-only
macro-triageagent per trace. Each agent checksenv.sandboxRevfirst, then classifies the trace against the failure taxonomy and returns a diagnosis with evidence. - Dedupes and ranks the findings.
- Turns each confirmed finding into a failing regression test through the
macro-fixtureagent before anyone writes a fix. - Appends the triaged filenames to
traces/.processed.
Trace bundles are large — never read one into the main context; the agents
exist for that. Nothing cleans traces/ automatically; delete it when you
finish. When you triage by hand, check env.sandboxRev first: a stale
sandbox reproduces bugs that are already fixed.
ui/state/appReducer.ts— one discriminated-union state machine with five modes (idle → recording → reviewing, andidle → playingdirectly or throughconfiguring, the pre-play parameter form).ui/gateways/types.ts— the three gateway interfaces the panel talks to;ui/gateways/index.tsis the single real-versus-mock seam.sandbox/serialize.tsandsandbox/applier.ts— the reads and the writes of Creator's live node proxies.sandbox/playback.tsandsandbox/recorder.tstouch a proxy only to resolve targets, run scene-level ops, and probe for diagnostics. Everything downstream is plain data.engine/testing/fakeScene.ts— the test double for the proxy surface, shared by the harness and vitest. It reproduces the host's traps on purpose, above all that astaticValuewrite does nothing while keyframes exist. Never make it more permissive than the real host.
- The layout holds a
min-width: 260pxand scrolls horizontally below that. - Sharing is Copy JSON and Import, not file export — Creator's
sandboxed iframe can block downloads.
docs/limitations.mdtracks the host limits with their evidence.
MIT — see LICENSE.