@a-company/atelier 0.29.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5QQESXI6.js +4432 -0
- package/dist/chunk-5QQESXI6.js.map +1 -0
- package/dist/cli.cjs +2391 -530
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +301 -429
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2233 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +584 -2
- package/dist/index.d.ts +584 -2
- package/dist/index.js +111 -3
- package/dist/mcp.cjs +1215 -365
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +1209 -365
- package/dist/mcp.js.map +1 -1
- package/package.json +20 -9
- package/src/web/inline-app.ts +867 -0
- package/src/web/tsconfig.json +9 -0
- package/templates/welcome.atelier +67 -0
- package/university/content/notes/N-atel-001-first-render.md +114 -0
- package/university/content/notes/N-atel-001-install-and-launch.md +84 -0
- package/university/content/notes/N-atel-001-what-is-atelier.md +51 -0
- package/university/content/notes/N-atel-101-easings.md +97 -0
- package/university/content/notes/N-atel-101-layers.md +106 -0
- package/university/content/notes/N-atel-101-states-and-deltas.md +94 -0
- package/university/content/notes/N-atel-101-the-atelier-format.md +72 -0
- package/university/content/notes/N-atel-201-authoring-tools.md +141 -0
- package/university/content/notes/N-atel-201-mcp-overview.md +86 -0
- package/university/content/notes/N-atel-201-patterns.md +108 -0
- package/university/content/notes/N-atel-201-visual-and-effects.md +125 -0
- package/university/content/notes/N-atel-301-composition-and-overlays.md +141 -0
- package/university/content/notes/N-atel-301-effects.md +136 -0
- package/university/content/notes/N-atel-301-images-and-video.md +126 -0
- package/university/content/notes/N-atel-301-shapes-and-text.md +118 -0
- package/university/content/notes/N-atel-401-hierarchical-states.md +71 -0
- package/university/content/notes/N-atel-401-motion-deep-dive.md +106 -0
- package/university/content/notes/N-atel-401-presets-and-templates.md +98 -0
- package/university/content/notes/N-atel-401-transitions.md +94 -0
- package/university/content/notes/N-atel-501-detected-vs-user-edited.md +76 -0
- package/university/content/notes/N-atel-501-layer-tag-isolation.md +62 -0
- package/university/content/notes/N-atel-501-silence-trim.md +98 -0
- package/university/content/notes/N-atel-501-transcribe-and-captions.md +98 -0
- package/university/content/notes/N-atel-601-carousel.md +71 -0
- package/university/content/notes/N-atel-601-overlay-rules.md +96 -0
- package/university/content/notes/N-atel-601-recipe-tools-and-apply.md +84 -0
- package/university/content/notes/N-atel-601-studio-recipe.md +103 -0
- package/university/content/notes/N-atel-701-choosing-output.md +68 -0
- package/university/content/notes/N-atel-701-png-and-frames.md +84 -0
- package/university/content/notes/N-atel-701-vector.md +85 -0
- package/university/content/notes/N-atel-701-video.md +88 -0
- package/university/content/notes/N-atel-801-editing-surface.md +69 -0
- package/university/content/notes/N-atel-801-live-bridge.md +84 -0
- package/university/content/notes/N-atel-801-studio-app.md +72 -0
- package/university/content/notes/N-atel-801-symbiotic-loop.md +56 -0
- package/university/content/paths/LP-atel-001.yaml +21 -0
- package/university/content/paths/LP-atel-101.yaml +22 -0
- package/university/content/paths/LP-atel-201.yaml +23 -0
- package/university/content/paths/LP-atel-301.yaml +22 -0
- package/university/content/paths/LP-atel-401.yaml +22 -0
- package/university/content/paths/LP-atel-501.yaml +22 -0
- package/university/content/paths/LP-atel-601.yaml +22 -0
- package/university/content/paths/LP-atel-701.yaml +22 -0
- package/university/content/paths/LP-atel-801.yaml +22 -0
- package/university/content/quizzes/Q-atel-001-orientation.yaml +66 -0
- package/university/content/quizzes/Q-atel-101-document-model.yaml +66 -0
- package/university/content/quizzes/Q-atel-201-mcp-authoring.yaml +66 -0
- package/university/content/quizzes/Q-atel-301-visual-system.yaml +66 -0
- package/university/content/quizzes/Q-atel-401-state-machines.yaml +66 -0
- package/university/content/quizzes/Q-atel-501-video-pipeline.yaml +66 -0
- package/university/content/quizzes/Q-atel-601-recipes.yaml +66 -0
- package/university/content/quizzes/Q-atel-701-export.yaml +66 -0
- package/university/content/quizzes/Q-atel-801-studio-loop.yaml +66 -0
- package/university/index.yaml +720 -0
- package/university/pack.yaml +21 -0
- package/dist/chunk-JV7RGETS.js +0 -2292
- package/dist/chunk-JV7RGETS.js.map +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-101-the-atelier-format
|
|
3
|
+
title: The `.atelier` document format
|
|
4
|
+
type: note
|
|
5
|
+
track: ATEL-101
|
|
6
|
+
author: atelier
|
|
7
|
+
created: '2026-05-18'
|
|
8
|
+
updated: '2026-05-18'
|
|
9
|
+
tags:
|
|
10
|
+
- course
|
|
11
|
+
- atel-101
|
|
12
|
+
- format
|
|
13
|
+
- schema
|
|
14
|
+
difficulty: beginner
|
|
15
|
+
estimatedMinutes: 4
|
|
16
|
+
prerequisites:
|
|
17
|
+
- N-atel-001-first-render
|
|
18
|
+
summary: The top-level shape of an `.atelier` YAML document — canvas, layers, states, assets, variables, presets, templates. Every field exists for one reason, named for what it means.
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## The five required parts
|
|
22
|
+
|
|
23
|
+
Every `.atelier` document is YAML with five mandatory top-level keys:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
name: My Animation # human-readable label
|
|
27
|
+
canvas: # global render context
|
|
28
|
+
width: 800
|
|
29
|
+
height: 600
|
|
30
|
+
fps: 60
|
|
31
|
+
background: "#0F1115" # optional, defaults to transparent
|
|
32
|
+
layers: [ ... ] # ordered array of visual elements
|
|
33
|
+
states: # named keyframes (always at least one)
|
|
34
|
+
default:
|
|
35
|
+
duration: 60 # in frames
|
|
36
|
+
deltas: [ ... ]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That is the minimum. Validators accept it. The renderer can resolve any frame in the `default` state.
|
|
40
|
+
|
|
41
|
+
## The optional parts
|
|
42
|
+
|
|
43
|
+
Any of these can be absent. Add them when you need them.
|
|
44
|
+
|
|
45
|
+
| Key | What it holds | When you need it |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `assets` | External resources (images, videos, audio) referenced by `assetId` from layers | The moment you add an image or video layer |
|
|
48
|
+
| `variables` | Named values referenced by `${name}` in templates and expression-driven deltas | When you want to parametrize a document or template |
|
|
49
|
+
| `presets` | Reusable delta bundles applied by name | When the same animation pattern repeats across layers |
|
|
50
|
+
| `templates` | Reusable layer subtrees instantiated by name with variable substitution | Carousels, lower-thirds, repeating motifs |
|
|
51
|
+
| `audio` | Audio track + sync metadata | Video projects with sound |
|
|
52
|
+
| `meta` | Free-form metadata (author, tags, source notes) | Anything you want a downstream tool to read |
|
|
53
|
+
|
|
54
|
+
## The order of keys is not meaningful (to the engine)
|
|
55
|
+
|
|
56
|
+
YAML is unordered for objects. The schema doesn't care whether `states` comes before `layers`. Conventionally we write them top-down — `name`, `canvas`, `assets`, `variables`, `presets`, `templates`, `layers`, `states`, `audio`, `meta` — because that order roughly matches "global setup → reusable bits → scene → motion → output."
|
|
57
|
+
|
|
58
|
+
## The order of `layers` IS meaningful
|
|
59
|
+
|
|
60
|
+
Layers render back-to-front. Index 0 is drawn first (background); the last layer is on top. This is also the order the layer panel shows them in the studio (top of the list = top of the stack, matching most design-tool conventions — the array is rendered bottom-up for display).
|
|
61
|
+
|
|
62
|
+
## Layer IDs and references
|
|
63
|
+
|
|
64
|
+
Every layer needs an `id` that is unique within the document. Deltas reference layers by id (`layer: title`). Parent-child relationships use `parentId`. Templates reference instance ids. The schema enforces uniqueness and rejects dangling references at validate time — you don't have to track them by hand.
|
|
65
|
+
|
|
66
|
+
Conventionally, layer ids are kebab-case (`title`, `subtitle`, `clip-trim-1`, `caption-word-7`). Pipeline-generated layers carry a tag namespace prefix in the id: `silence-trim-N`, `caption-N`, `caption-word-N`, `overlay-N`. The tags drive the layer-tag isolation invariant (ATEL-501).
|
|
67
|
+
|
|
68
|
+
## Reading order for the rest of ATEL-101
|
|
69
|
+
|
|
70
|
+
1. **Layers** — anatomy of a single layer: visual, frame, bounds, anchor, opacity, rotation, scale, parent, visible, motion path, clip path, blend mode, shadow, tint, interactions
|
|
71
|
+
2. **States and deltas** — what a state is, what a delta is, how `resolveFrame(doc, stateName, frame)` produces a renderable scene
|
|
72
|
+
3. **Easings** — linear / cubic-bezier / ease presets / spring / step; the math you don't have to remember but should recognize
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-201-authoring-tools
|
|
3
|
+
title: Authoring tools — document, layer, state, delta
|
|
4
|
+
type: note
|
|
5
|
+
track: ATEL-201
|
|
6
|
+
author: atelier
|
|
7
|
+
created: '2026-05-18'
|
|
8
|
+
updated: '2026-05-18'
|
|
9
|
+
tags:
|
|
10
|
+
- course
|
|
11
|
+
- atel-201
|
|
12
|
+
- mcp
|
|
13
|
+
- authoring
|
|
14
|
+
difficulty: intermediate
|
|
15
|
+
estimatedMinutes: 6
|
|
16
|
+
prerequisites:
|
|
17
|
+
- N-atel-201-mcp-overview
|
|
18
|
+
summary: The tools you reach for to build animations. Document lifecycle, layer composition, state machines, delta authoring. Annotated by which to call when.
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Document lifecycle
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
atelier_create — make a new document with name + canvas
|
|
25
|
+
atelier_load — load a .atelier file from disk by path
|
|
26
|
+
atelier_list — list .atelier files in CWD
|
|
27
|
+
atelier_info — return name + canvas + layer count + state count
|
|
28
|
+
atelier_validate — schema-validate the current doc; returns errors with paths
|
|
29
|
+
atelier_preview — resolve one frame and return ResolvedLayer[]
|
|
30
|
+
atelier_batch_preview — resolve multiple frames in one call (for thumbnail rows)
|
|
31
|
+
atelier_diff_frames — compare two resolved frames; return property-level diffs
|
|
32
|
+
atelier_profile — measure resolveFrame latency per state
|
|
33
|
+
atelier_complexity — count layers, deltas, refs, max nesting; flag perf risks
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**When to use which:**
|
|
37
|
+
|
|
38
|
+
- Starting from scratch: `atelier_create({ name, canvas: { width, height, fps }})`. Returns a minimal doc with one empty `default` state.
|
|
39
|
+
- Resuming on an existing file: `atelier_load({ path })`. The store now holds that doc; subsequent calls operate on it.
|
|
40
|
+
- Sanity-checking before you ship: `atelier_validate` then `atelier_complexity` — the first catches schema issues, the second flags renderer perf risks (1000+ layers, 10+ levels of group nesting, etc.).
|
|
41
|
+
- Visual debugging: `atelier_preview({ frame: 30 })` returns the same `ResolvedLayer[]` the renderer consumes. Compare against `atelier_diff_frames({ from: 29, to: 31 })` to find what changes.
|
|
42
|
+
|
|
43
|
+
## Layer composition
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
atelier_add_layer — add a layer with visual + frame + bounds
|
|
47
|
+
atelier_edit_layer — edit non-visual layer properties (id, tags, parentId, etc.)
|
|
48
|
+
atelier_remove_layer — remove a layer by id (and any deltas referencing it)
|
|
49
|
+
atelier_reorder — change layer z-order
|
|
50
|
+
atelier_list_layers — list all layer ids + types
|
|
51
|
+
atelier_edit_visual — replace or patch the visual block (shape→text, fill, etc.)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Pattern:** build all layers first, then animate. Adding layers triggers immediate validation; adding deltas requires the target layer to exist. Reverse order works but emits more errors.
|
|
55
|
+
|
|
56
|
+
**`atelier_edit_visual` is the underrated one.** It's the single tool for "I want this rect to become a text layer" or "change this image's `assetId`." Most edit-flow agents use this far more than the dedicated `atelier_set_*` family.
|
|
57
|
+
|
|
58
|
+
## State machines
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
atelier_add_state — name a new state with duration (frames)
|
|
62
|
+
atelier_edit_state — change duration, parent state, transitions
|
|
63
|
+
atelier_remove_state — remove a state (rejects if it's the only one)
|
|
64
|
+
atelier_list_states — list state names + durations + parent chain
|
|
65
|
+
atelier_set_state_parent — make a state inherit deltas from another
|
|
66
|
+
atelier_configure_transition — define transition from state A→B with easing
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**State machines are the foundation of interactive motion.** A `default` state shows the resting layout. `hover` extends `default` (via `set_state_parent`) with extra deltas. `pressed` transitions from `hover` with a 200ms ease-out (via `configure_transition`). Three states, total complexity stays low, the interactive feel is right.
|
|
70
|
+
|
|
71
|
+
For non-interactive animations: one `default` state is fine. State-machine complexity is opt-in.
|
|
72
|
+
|
|
73
|
+
## Delta authoring
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
atelier_add_delta — animate one property of one layer
|
|
77
|
+
atelier_edit_delta — change from/to/easing/range of an existing delta
|
|
78
|
+
atelier_remove_delta — remove a delta
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`atelier_add_delta` takes:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
{
|
|
85
|
+
state: "default",
|
|
86
|
+
layer: "title",
|
|
87
|
+
property: "opacity", // or "frame.x", "style.color", "scale.x", ...
|
|
88
|
+
from: 0,
|
|
89
|
+
to: 1,
|
|
90
|
+
startFrame: 0,
|
|
91
|
+
endFrame: 30,
|
|
92
|
+
easing: "ease-out" // or cubic-bezier(...), spring({...}), step(...)
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The **no-overlap gate** rejects two deltas on the same `layer` + `property` whose `[startFrame, endFrame]` ranges overlap. If you need composed motion (e.g. wobble overlaid on a slide), use two layers or a `group`. The error message names both deltas; the fix is obvious.
|
|
97
|
+
|
|
98
|
+
**Easing as an object, not just a string.** The string form (`"ease-out"`) is the preset shortcut; the object form (`{ type: "spring", stiffness: 170, damping: 26 }`) is the full surface. Use the object form when you want spring physics or a custom cubic-bezier with specific control points.
|
|
99
|
+
|
|
100
|
+
## A complete worked example
|
|
101
|
+
|
|
102
|
+
Building "fade in title, slide subtitle from below, hold for 2 seconds" from zero:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const { documentId } = await atelier_create({
|
|
106
|
+
name: "Intro", canvas: { width: 1920, height: 1080, fps: 60 }
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await atelier_add_layer({ documentId, layer: {
|
|
110
|
+
id: "title", visual: { type: "text", content: "Atelier",
|
|
111
|
+
style: { fontFamily: "Inter", fontSize: 120, color: "#F5F5F7" }},
|
|
112
|
+
frame: { x: 960, y: 480 }, bounds: { width: 1200, height: 160 },
|
|
113
|
+
anchorPoint: { x: 0.5, y: 0.5 }, opacity: 0
|
|
114
|
+
}});
|
|
115
|
+
|
|
116
|
+
await atelier_add_layer({ documentId, layer: {
|
|
117
|
+
id: "subtitle", visual: { type: "text", content: "AI-native animation",
|
|
118
|
+
style: { fontFamily: "Inter", fontSize: 40, color: "#9CA3AF" }},
|
|
119
|
+
frame: { x: 960, y: 580 }, bounds: { width: 1200, height: 60 },
|
|
120
|
+
anchorPoint: { x: 0.5, y: 0.5 }, opacity: 0
|
|
121
|
+
}});
|
|
122
|
+
|
|
123
|
+
await atelier_edit_state({ documentId, state: "default", duration: 180 });
|
|
124
|
+
|
|
125
|
+
await atelier_add_delta({ documentId, state: "default", layer: "title",
|
|
126
|
+
property: "opacity", from: 0, to: 1, startFrame: 0, endFrame: 30,
|
|
127
|
+
easing: "ease-out" });
|
|
128
|
+
|
|
129
|
+
await atelier_add_delta({ documentId, state: "default", layer: "subtitle",
|
|
130
|
+
property: "opacity", from: 0, to: 1, startFrame: 20, endFrame: 50,
|
|
131
|
+
easing: "ease-out" });
|
|
132
|
+
|
|
133
|
+
await atelier_add_delta({ documentId, state: "default", layer: "subtitle",
|
|
134
|
+
property: "frame.y", from: 680, to: 580, startFrame: 20, endFrame: 50,
|
|
135
|
+
easing: { type: "spring", stiffness: 120, damping: 22 } });
|
|
136
|
+
|
|
137
|
+
await atelier_validate({ documentId });
|
|
138
|
+
await atelier_preview({ documentId, frame: 40 }); // inspect mid-motion
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Eight calls. Two layers, three deltas, one validate, one preview. The agent never touched YAML.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-201-mcp-overview
|
|
3
|
+
title: MCP in the Atelier context
|
|
4
|
+
type: note
|
|
5
|
+
track: ATEL-201
|
|
6
|
+
author: atelier
|
|
7
|
+
created: '2026-05-18'
|
|
8
|
+
updated: '2026-05-18'
|
|
9
|
+
tags:
|
|
10
|
+
- course
|
|
11
|
+
- atel-201
|
|
12
|
+
- mcp
|
|
13
|
+
- architecture
|
|
14
|
+
difficulty: intermediate
|
|
15
|
+
estimatedMinutes: 4
|
|
16
|
+
prerequisites:
|
|
17
|
+
- N-atel-101-easings
|
|
18
|
+
summary: What the `atelier-mcp` server actually is, why every authoring tool maps to a single mutation on a single document store, and how the LLM↔Studio live-mutation bridge works without conflict.
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What `atelier-mcp` is
|
|
22
|
+
|
|
23
|
+
`atelier-mcp` is a Model Context Protocol server. An AI agent connects to it (via stdio in Claude Desktop, or WebSocket when paired with `atelier studio`) and gains 61 tools across 15 groups. Each tool is a typed function the agent calls; each call is a single mutation against a shared in-memory `DocumentStore`. The server doesn't talk to the LLM; it just exposes the mutation surface and lets the LLM drive.
|
|
24
|
+
|
|
25
|
+
The boundary is intentional: the agent does the planning, the server does the validating. If the agent asks `atelier_add_delta` to animate a property that doesn't exist on a layer, the Zod schema rejects the call with an AI-readable error message. The agent reads the error, fixes the request, retries. No invalid document ever reaches the renderer.
|
|
26
|
+
|
|
27
|
+
## The single-store invariant
|
|
28
|
+
|
|
29
|
+
There is **one** `DocumentStore` per project session. Every client — the MCP server's tool handlers, the `atelier studio` browser canvas, the WebSocket bridge between them — reads and writes the same store.
|
|
30
|
+
|
|
31
|
+
This is why the live LLM-mutation feature works at all. When an agent calls `atelier_add_layer`, the store updates, fires `onChange`, and the bridge broadcasts the mutation to any connected Studio. The Studio applies the mutation via `applyMutation(op)` (with a suppress-notify flag for one tick so the change doesn't echo back as a human edit), re-renders the canvas, and shows a toast: *"agent added layer `title`"* with an Undo affordance.
|
|
32
|
+
|
|
33
|
+
Conflict policy in v1: **last-write-wins**. Human edits during agent mutation overwrite the agent's pending change. No CRDT, no operational transform — Atelier is a single-author tool with an AI collaborator, not a real-time multiplayer canvas. Every op is tagged `source: "human" | "llm"` so the UI can attribute changes.
|
|
34
|
+
|
|
35
|
+
## Tool conventions
|
|
36
|
+
|
|
37
|
+
Every MCP tool follows the same conventions. Recognize the pattern once and you can read any tool's signature:
|
|
38
|
+
|
|
39
|
+
| Convention | Example |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `atelier_<verb>_<noun>` naming | `atelier_add_layer`, `atelier_edit_delta`, `atelier_remove_state` |
|
|
42
|
+
| First argument is always `documentId` (or implicit if the server has a current doc) | `atelier_add_layer({ documentId, ... })` |
|
|
43
|
+
| Mutations return the **updated document** (not just the changed fragment) | An agent doesn't have to re-fetch after every call |
|
|
44
|
+
| Inspect tools return only what was asked for | `atelier_preview` returns resolved layers, not the whole doc |
|
|
45
|
+
| Validation runs after every mutation; tool calls fail fast with field-level errors | Errors include the offending path: `layers[0].visual.style.fontSize: expected number` |
|
|
46
|
+
| Tools that need a frame number accept either an absolute frame or `{ state, frame }` | Clarity over cleverness |
|
|
47
|
+
|
|
48
|
+
## The 15 groups
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
document — create, load, list, info, validate, preview, batch_preview,
|
|
52
|
+
profile, complexity, diff_frames, export, export_svg, export_lottie
|
|
53
|
+
layer — add, edit, remove, reorder, list, edit_visual
|
|
54
|
+
state — add, edit, remove, list, set_parent, configure_transition
|
|
55
|
+
delta — add, edit, remove
|
|
56
|
+
visual — set_shape, set_fill, set_stroke, set_shadow, set_blend_mode,
|
|
57
|
+
set_clip_path, set_motion_path, set_tint
|
|
58
|
+
asset — add, list, remove
|
|
59
|
+
variable — add, list, remove, find
|
|
60
|
+
preset — define, list, apply
|
|
61
|
+
template — instantiate
|
|
62
|
+
interaction — add, list, remove
|
|
63
|
+
ref — set_ref, resolve_refs
|
|
64
|
+
audio — set_audio
|
|
65
|
+
overlay — add_handle, add_page_number (Phase 1.5)
|
|
66
|
+
recipe — recipe_list, recipe_get, recipe_validate, recipe_save, recipe_apply
|
|
67
|
+
import — import_images
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The next three notes cover the high-frequency tools — what to call and when. The full reference is `docs/mcp-reference.md` in the atelier repo (and at `https://atelier.dev/mcp`, when that lands).
|
|
71
|
+
|
|
72
|
+
## Configuring the server
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"atelier": {
|
|
78
|
+
"command": "atelier-mcp"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
That's it. No auth tokens. No project path — the server scans CWD for `.atelier` files and loads them lazily on `atelier_load`. To bind the server to a specific project directory, set `ATELIER_PROJECT_ROOT=/path/to/project` in the env.
|
|
85
|
+
|
|
86
|
+
For the live Studio bridge, run `atelier studio` first; the MCP server detects the bridge's WebSocket endpoint and connects automatically. No config needed.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-201-patterns
|
|
3
|
+
title: Authoring patterns and decision rules
|
|
4
|
+
type: note
|
|
5
|
+
track: ATEL-201
|
|
6
|
+
author: atelier
|
|
7
|
+
created: '2026-05-18'
|
|
8
|
+
updated: '2026-05-18'
|
|
9
|
+
tags:
|
|
10
|
+
- course
|
|
11
|
+
- atel-201
|
|
12
|
+
- mcp
|
|
13
|
+
- patterns
|
|
14
|
+
- best-practices
|
|
15
|
+
difficulty: intermediate
|
|
16
|
+
estimatedMinutes: 4
|
|
17
|
+
prerequisites:
|
|
18
|
+
- N-atel-201-visual-and-effects
|
|
19
|
+
summary: The high-frequency patterns — build-then-animate, snapshot-edit-restore, batch-mutate-validate, preset-vs-hand-author. When to reach for which tool, and the four mistakes agents make most.
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Pattern 1 — Build, then animate
|
|
23
|
+
|
|
24
|
+
Adding deltas before layers is rejected (the delta's target layer doesn't exist yet). Adding all layers first, then all deltas in the order they'll fire, produces clean diffs and easy debugging.
|
|
25
|
+
|
|
26
|
+
Rough order for a fresh document:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
atelier_create
|
|
30
|
+
↓ (variables / assets / presets / templates if needed)
|
|
31
|
+
atelier_add_variable (× N)
|
|
32
|
+
atelier_add_asset (× N)
|
|
33
|
+
atelier_define_preset (× N)
|
|
34
|
+
↓
|
|
35
|
+
atelier_add_layer (× N)
|
|
36
|
+
atelier_edit_visual / set_* (× N — visual configuration that doesn't animate)
|
|
37
|
+
↓
|
|
38
|
+
atelier_add_state (× N — if multi-state)
|
|
39
|
+
atelier_set_state_parent (× N — for hierarchical states)
|
|
40
|
+
atelier_configure_transition (× N — for interactive transitions)
|
|
41
|
+
↓
|
|
42
|
+
atelier_add_delta (× N — the motion)
|
|
43
|
+
↓
|
|
44
|
+
atelier_validate
|
|
45
|
+
atelier_preview { frame: N } (sanity-check mid-motion)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Reverse-order works (the validator still catches problems) but produces noisier error logs.
|
|
49
|
+
|
|
50
|
+
## Pattern 2 — Snapshot, edit, restore
|
|
51
|
+
|
|
52
|
+
When an agent is about to make a destructive change (e.g. restructuring states, replacing a layer type), snapshot first:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const before = await atelier_info({ documentId });
|
|
56
|
+
// ... mutations ...
|
|
57
|
+
await atelier_validate({ documentId });
|
|
58
|
+
// if it doesn't pass: reload from the prior state or use the studio's undo stack
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The MCP store doesn't have an undo stack of its own (yet — that's Studio-side via `History`). For agent workflows: persist the doc with `atelier_export` before risky changes, restore from the export if validation fails.
|
|
62
|
+
|
|
63
|
+
## Pattern 3 — Batch-mutate-validate
|
|
64
|
+
|
|
65
|
+
Many small mutations followed by one `atelier_validate` is more efficient than validating after every call. The schema runs on every tool call regardless (you can't bypass it), but `atelier_validate` also reports advisory warnings — long pauses, overly-deep nesting, missing transitions — that you don't want firing 50 times during a build.
|
|
66
|
+
|
|
67
|
+
For very large compositions (50+ layers), the studio bridge benefits from grouping mutations into a "build session" — start with `atelier_create`, end with one validate, broadcast one `doc:patch` instead of 50 incremental `llm:mutation` events. The bridge's `coalesce` flag (when set on the WS envelope) suppresses incremental broadcasts until the session ends.
|
|
68
|
+
|
|
69
|
+
## Pattern 4 — Preset vs. hand-author
|
|
70
|
+
|
|
71
|
+
If the same motion repeats across three or more layers: define a preset.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
await atelier_define_preset({ documentId, name: "fade-in-from-below",
|
|
75
|
+
deltas: [
|
|
76
|
+
{ property: "opacity", from: 0, to: 1, startFrame: 0, endFrame: 24, easing: "ease-out" },
|
|
77
|
+
{ property: "frame.y", from: "${baseY + 24}", to: "${baseY}", startFrame: 0, endFrame: 24, easing: { type: "spring", stiffness: 120, damping: 22 }}
|
|
78
|
+
]});
|
|
79
|
+
|
|
80
|
+
for (const layerId of ["title", "subtitle", "caption"]) {
|
|
81
|
+
await atelier_apply_preset({ documentId, name: "fade-in-from-below",
|
|
82
|
+
layer: layerId, state: "default", offset: layerId === "title" ? 0 : 6 });
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The `offset` parameter shifts the applied deltas by N frames — that's how you stagger entrances without hand-authoring N delta sets.
|
|
87
|
+
|
|
88
|
+
If a motion is one-of-a-kind: hand-author. Don't define a preset for a one-time animation.
|
|
89
|
+
|
|
90
|
+
## Pattern 5 — Use templates for *layers*, presets for *deltas*
|
|
91
|
+
|
|
92
|
+
Templates produce layer subtrees (with variable substitution). Presets produce delta bundles.
|
|
93
|
+
|
|
94
|
+
If you find yourself defining a "preset" that's actually a `group` layer with three children — that's a template. Switch and use `atelier_instantiate_template`.
|
|
95
|
+
|
|
96
|
+
If you find yourself defining a "template" that's just three deltas on a single layer — that's a preset. Switch and use `atelier_define_preset`.
|
|
97
|
+
|
|
98
|
+
## The four most common agent mistakes
|
|
99
|
+
|
|
100
|
+
1. **Adding a delta to a property that doesn't exist on the layer's visual type.** A `TextVisual` has `style.fontSize` but no `style.borderRadius`. The schema rejects it; the agent should read the error message verbatim ("`property "style.borderRadius" is not animatable on a text layer`") and pick a real property.
|
|
101
|
+
|
|
102
|
+
2. **Forgetting to set `endFrame ≤ state.duration`.** The schema accepts deltas that extend past the state's duration, but the renderer clamps them — you get motion that "freezes" at the boundary value. If you want a 2-second animation, set `state.duration: 120` (at 60fps) before adding deltas.
|
|
103
|
+
|
|
104
|
+
3. **Overlapping deltas on the same layer + property.** The no-overlap gate rejects it. Fix: split into two adjacent ranges (`0–30` then `30–60`), or use two layers.
|
|
105
|
+
|
|
106
|
+
4. **Mutating a doc without saving.** The MCP store is in-memory. Closing the session loses unsaved work. Either run inside `atelier studio` (which autosaves), or call `atelier_export({ format: "yaml", out: "session.atelier" })` periodically.
|
|
107
|
+
|
|
108
|
+
That closes ATEL-201. The next track (ATEL-301) covers the visual system in depth — every shape, every fill mode, the typography stack, image handling, and the new overlay namespace.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-201-visual-and-effects
|
|
3
|
+
title: Visual configuration, effects, assets, and refs
|
|
4
|
+
type: note
|
|
5
|
+
track: ATEL-201
|
|
6
|
+
author: atelier
|
|
7
|
+
created: '2026-05-18'
|
|
8
|
+
updated: '2026-05-18'
|
|
9
|
+
tags:
|
|
10
|
+
- course
|
|
11
|
+
- atel-201
|
|
12
|
+
- mcp
|
|
13
|
+
- visual
|
|
14
|
+
- assets
|
|
15
|
+
difficulty: intermediate
|
|
16
|
+
estimatedMinutes: 5
|
|
17
|
+
prerequisites:
|
|
18
|
+
- N-atel-201-authoring-tools
|
|
19
|
+
summary: The visual configuration toolset — shape, fill, stroke, shadow, blend mode, clip path, motion path, tint. Plus assets, variables, presets, templates, interactions, refs, audio, and the overlay namespace.
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Visual configuration tools
|
|
23
|
+
|
|
24
|
+
These tools mutate a layer's `visual` or layer-level effect properties:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
atelier_set_shape — change a ShapeVisual's shape (rect → ellipse → path)
|
|
28
|
+
atelier_set_fill — set/clear fill color, gradient, or pattern
|
|
29
|
+
atelier_set_stroke — set/clear stroke color, width, dash pattern
|
|
30
|
+
atelier_set_shadow — drop shadow / glow on the layer
|
|
31
|
+
atelier_set_blend_mode — Canvas composite operation (multiply, screen, etc.)
|
|
32
|
+
atelier_set_clip_path — restrict rendering to inside a shape
|
|
33
|
+
atelier_set_motion_path — animate position along a path with optional auto-rotate
|
|
34
|
+
atelier_set_tint — color overlay strength 0-1
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Each is a single mutation. The tools exist (instead of bundling into `atelier_edit_visual`) because their argument shapes are stable — fill takes a color/gradient/pattern union, stroke takes width + color + dash, shadow takes offset + blur + color. Type narrowing in your IDE makes them friendly to call.
|
|
38
|
+
|
|
39
|
+
**Fill, stroke, and shadow are animatable.** A delta on `fill.color` or `shadow.blur` interpolates the value frame-by-frame. The visual-config tools set the *current* (static) value; deltas animate it.
|
|
40
|
+
|
|
41
|
+
## Assets
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
atelier_add_asset — register an image / video / audio asset (assetId + src)
|
|
45
|
+
atelier_list_assets — enumerate registered assets
|
|
46
|
+
atelier_remove_asset — remove (rejects if referenced by a layer)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Assets are referenced by `assetId` from `ImageVisual.assetId`, `VideoVisual.assetId`, and the audio block. Registering an asset doesn't load it — the renderer fetches lazily. Use relative paths (relative to the doc) or absolute file:// URLs; the studio's `/api/assets/:base64path` endpoint serves them with path-traversal protection.
|
|
50
|
+
|
|
51
|
+
## Variables
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
atelier_add_variable — declare a named variable with a default value
|
|
55
|
+
atelier_list_variables — enumerate variables
|
|
56
|
+
atelier_remove_variable — remove a variable (rejects if referenced)
|
|
57
|
+
atelier_find_variables — find layers/deltas using ${name} substitution
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Variables enable templates and parametric documents. A `${title}` in a text layer's `content` is substituted at resolve time. The recipe system (ATEL-601) uses variables to inject `{current}`/`{total}` for page-number overlays.
|
|
61
|
+
|
|
62
|
+
## Presets and templates
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
atelier_define_preset — bundle a set of deltas as a named preset
|
|
66
|
+
atelier_list_presets — enumerate presets
|
|
67
|
+
atelier_apply_preset — apply a preset's deltas to one or more layers
|
|
68
|
+
atelier_instantiate_template — instantiate a named layer subtree with variable substitution
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Preset** = "I want this fade-in pattern on every title." Define it once with `atelier_define_preset`, apply it to N layers with `atelier_apply_preset` — each application substitutes the layer id and produces deltas.
|
|
72
|
+
|
|
73
|
+
**Template** = "I want this layout (logo + handle + page-number) in every carousel page." Define a template subtree with variable holes; `atelier_instantiate_template({ template, variables: { handle: "@me", page: "01/05" }})` produces a fresh subtree with those values.
|
|
74
|
+
|
|
75
|
+
The studio recipe system (ATEL-601) is partially built on templates — applying a recipe instantiates a default template per layer type if no user-authored layer occupies that slot.
|
|
76
|
+
|
|
77
|
+
## Interactions
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
atelier_add_interaction — bind an event (hover, click) to an action (state transition)
|
|
81
|
+
atelier_list_interactions — enumerate interactions on the document
|
|
82
|
+
atelier_remove_interaction — remove an interaction
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Interactions live on layers and trigger state transitions. `hover` on the title layer transitions the document into the `hover` state with the transition easing defined by `atelier_configure_transition`. The renderer's interactive runtime (used by the studio, by SVG embeds, and by `@atelier/react`) consumes these.
|
|
86
|
+
|
|
87
|
+
For pure video output: skip interactions. They're for interactive embeds.
|
|
88
|
+
|
|
89
|
+
## Refs (sub-documents)
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
atelier_set_ref — point a RefVisual layer at another .atelier file
|
|
93
|
+
atelier_resolve_refs — preview the rendered ref tree (for nested refs)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
A `RefVisual` layer renders another `.atelier` document inside itself. Use for: reusable lower-thirds across multiple compositions, lock-up logos that need a single source of truth, carousels where every page references a shared layout template.
|
|
97
|
+
|
|
98
|
+
Refs are resolved at render time. The studio re-renders the ref when the source file changes (file watcher), so editing the referenced doc updates every composition that uses it.
|
|
99
|
+
|
|
100
|
+
## Audio
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
atelier_set_audio — attach an audio track to the document with sync metadata
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For video projects with sound. The `atelier transcribe` pipeline (ATEL-501) writes the transcript and the audio sync metadata that drives caption layer alignment.
|
|
107
|
+
|
|
108
|
+
## Overlays (Phase 1.5)
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
atelier_add_handle — create a @handle text overlay layer (tag: overlay)
|
|
112
|
+
atelier_add_page_number — render {current}/{total} into a text overlay layer
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
These are convenience tools matching the recipe `overlay_rules` schema. Layers carry `tags: ["overlay"]` so they participate in the layer-tag isolation invariant — re-applying an overlay recipe drops only overlay-tagged layers and replaces them; user-authored layers and other pipelines' outputs are untouched.
|
|
116
|
+
|
|
117
|
+
## Export
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
atelier_export — top-level export (auto-detects format from --out extension)
|
|
121
|
+
atelier_export_svg — SVG string of one frame
|
|
122
|
+
atelier_export_lottie — Lottie JSON of the full document
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Export tools call into `@atelier/canvas`, `@atelier/svg`, `@atelier/lottie`, or `@atelier/video` depending on format. Single-frame PNG, multi-frame MP4/WebM/GIF, vector SVG, and motion-design Lottie all land here.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-301-composition-and-overlays
|
|
3
|
+
title: Composition, refs, and the overlay namespace
|
|
4
|
+
type: note
|
|
5
|
+
track: ATEL-301
|
|
6
|
+
author: atelier
|
|
7
|
+
created: '2026-05-18'
|
|
8
|
+
updated: '2026-05-18'
|
|
9
|
+
tags:
|
|
10
|
+
- course
|
|
11
|
+
- atel-301
|
|
12
|
+
- composition
|
|
13
|
+
- overlays
|
|
14
|
+
- refs
|
|
15
|
+
- groups
|
|
16
|
+
difficulty: intermediate
|
|
17
|
+
estimatedMinutes: 5
|
|
18
|
+
prerequisites:
|
|
19
|
+
- N-atel-301-effects
|
|
20
|
+
summary: GroupVisual + parentId for transform-inherited clusters, RefVisual for sub-documents, and the `overlay` tag namespace — handle and page-number overlays as parameterized text layers protected by the layer-tag isolation invariant.
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Groups and parentId
|
|
24
|
+
|
|
25
|
+
A `GroupVisual` layer renders nothing of its own. It exists to provide a parent transform for child layers:
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
- id: card
|
|
29
|
+
visual: { type: group }
|
|
30
|
+
frame: { x: 200, y: 400 }
|
|
31
|
+
rotation: -8
|
|
32
|
+
bounds: { width: 600, height: 800 }
|
|
33
|
+
|
|
34
|
+
- id: card-photo
|
|
35
|
+
parentId: card
|
|
36
|
+
visual: { type: image, assetId: hero-photo }
|
|
37
|
+
frame: { x: 0, y: 0 }
|
|
38
|
+
bounds: { width: 600, height: 600 }
|
|
39
|
+
|
|
40
|
+
- id: card-title
|
|
41
|
+
parentId: card
|
|
42
|
+
visual: { type: text, content: "Title", style: { ... }}
|
|
43
|
+
frame: { x: 24, y: 640 }
|
|
44
|
+
bounds: { width: 552, height: 80 }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `card` group is positioned at canvas (200, 400) and rotated -8°. Its children render in the group's local coordinate space — the photo is at (0, 0) relative to the rotated card, not the canvas. Animating the group's `rotation` or `frame.x` moves the whole cluster as one unit.
|
|
48
|
+
|
|
49
|
+
**Use groups for:** anything that needs to move/rotate/scale as a unit. Cards, lower-thirds, picture-in-picture clusters, repeated elements you want to manage together.
|
|
50
|
+
|
|
51
|
+
**Don't use groups for:** ordering or styling. Layer z-order is the array index; styling is per-layer.
|
|
52
|
+
|
|
53
|
+
## Refs — sub-documents
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
- id: lower-third
|
|
57
|
+
visual:
|
|
58
|
+
type: ref
|
|
59
|
+
src: ./lower-third.atelier
|
|
60
|
+
state: default # optional — defaults to first state
|
|
61
|
+
frame: 0 # optional — defaults to parent frame, clamped to sub-doc duration
|
|
62
|
+
frame: { x: 0, y: 900 }
|
|
63
|
+
bounds: { width: 1920, height: 180 }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A `RefVisual` renders another `.atelier` document inside itself. The sub-document is resolved (via `resolveFrame`) and rendered into the parent's coordinate space.
|
|
67
|
+
|
|
68
|
+
**Use refs for:** brand lock-ups (logo + tagline) reused across compositions, lower-thirds with consistent style, carousel templates where every page references a shared layout, scenes that are themselves composed scenes.
|
|
69
|
+
|
|
70
|
+
The studio file-watches every referenced `.atelier` file. Editing the sub-document updates every parent composition that references it — refs are how Atelier expresses "single source of truth."
|
|
71
|
+
|
|
72
|
+
## The overlay namespace
|
|
73
|
+
|
|
74
|
+
The `overlay` tag namespace is a Phase 1.5 first-class slot for decorative text layers that mark a composition (handle, page-number, credit, date, QR). Every overlay layer carries `tags: ["overlay"]`.
|
|
75
|
+
|
|
76
|
+
The contract:
|
|
77
|
+
|
|
78
|
+
1. Pipelines and recipes that produce overlays use `applyRecipeToOverlay`, which drops only `overlay`-tagged layers before re-adding. The layer-tag isolation invariant (ATEL-501) applies.
|
|
79
|
+
2. User edits to overlay layers (move, restyle, hide) survive re-runs as long as the layer id matches a recipe-defined overlay (the `userEdited` flag is preserved across re-applications).
|
|
80
|
+
3. MCP convenience tools (`atelier_add_handle`, `atelier_add_page_number`) emit overlay-tagged layers with sensible defaults; recipes generate the same shape declaratively.
|
|
81
|
+
|
|
82
|
+
### Handle overlay
|
|
83
|
+
|
|
84
|
+
A handle is a static text overlay — typically a social-media handle (`@username`), credit (`by Author`), or watermark. Authored:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
overlay_rules:
|
|
88
|
+
handle:
|
|
89
|
+
text: "@username"
|
|
90
|
+
anchor: bottom-left
|
|
91
|
+
margin: 32
|
|
92
|
+
style:
|
|
93
|
+
fontFamily: Inter
|
|
94
|
+
fontSize: 24
|
|
95
|
+
fontWeight: 600
|
|
96
|
+
color: "#F5F5F7"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Applied via `apply-recipe` or `atelier_add_handle`. Layer id: `overlay-handle` (stable, so re-apply replaces in place).
|
|
100
|
+
|
|
101
|
+
### Page-number overlay
|
|
102
|
+
|
|
103
|
+
A page-number is a parameterized text overlay rendering a template like `{current}/{total}` or `{current:02d} of {total:02d}`. Authored:
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
overlay_rules:
|
|
107
|
+
page_number:
|
|
108
|
+
format: "{current:02d}/{total:02d}"
|
|
109
|
+
anchor: bottom-right
|
|
110
|
+
margin: 32
|
|
111
|
+
style:
|
|
112
|
+
fontFamily: Inter
|
|
113
|
+
fontSize: 18
|
|
114
|
+
fontWeight: 500
|
|
115
|
+
color: "#9CA3AF"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Applied during carousel batch processing: the renderer threads `currentIndex` and `totalCount` into `applyRecipeToOverlay`, which renders the format string into the layer's content. Layer id: `overlay-page-number`.
|
|
119
|
+
|
|
120
|
+
Single-frame applies (where the carousel context isn't available) skip the page-number layer with a logger warning — by design. A page-number doesn't make sense on a standalone composition.
|
|
121
|
+
|
|
122
|
+
### Anchor math
|
|
123
|
+
|
|
124
|
+
All four corners are supported. The translator maps anchor → frame/anchorPoint as follows:
|
|
125
|
+
|
|
126
|
+
| `anchor` | `frame` | `anchorPoint` |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `top-left` | `{ x: margin, y: margin }` | `{ x: 0, y: 0 }` |
|
|
129
|
+
| `top-right` | `{ x: canvas.width - margin, y: margin }` | `{ x: 1, y: 0 }` |
|
|
130
|
+
| `bottom-left` | `{ x: margin, y: canvas.height - margin }` | `{ x: 0, y: 1 }` |
|
|
131
|
+
| `bottom-right` | `{ x: canvas.width - margin, y: canvas.height - margin }` | `{ x: 1, y: 1 }` |
|
|
132
|
+
|
|
133
|
+
The anchor doubles as the rotation/scale pivot — animating `rotation` on an overlay rotates around the corner the overlay is anchored to.
|
|
134
|
+
|
|
135
|
+
### Why overlays are their own namespace (and not just "text layers")
|
|
136
|
+
|
|
137
|
+
Because they're **generated and re-generated** by recipes. The layer-tag isolation invariant lets a user tweak an overlay (drag the handle a few pixels, restyle it), re-apply the recipe, and keep the tweak. Untagged user-authored text layers are never touched. Caption-pipeline layers (`tags: ["caption"]`) and silence-trim layers (`tags: ["silence-trim"]`) are never touched either.
|
|
138
|
+
|
|
139
|
+
That isolation is what makes the human↔agent symbiotic loop safe at every level of the pipeline — silence-trim, captions, overlays, and (soon) carousel-batch page-numbers.
|
|
140
|
+
|
|
141
|
+
That closes ATEL-301. You now know every visual primitive Atelier renders and how composition + overlays slot into the layer-tag invariant. The remaining tracks (ATEL-401 motion deep-dive, ATEL-501 video pipelines, ATEL-601 recipes, ATEL-701 export) layer on top of this foundation.
|