@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.
Files changed (76) hide show
  1. package/dist/chunk-5QQESXI6.js +4432 -0
  2. package/dist/chunk-5QQESXI6.js.map +1 -0
  3. package/dist/cli.cjs +2391 -530
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +301 -429
  6. package/dist/cli.js.map +1 -1
  7. package/dist/index.cjs +2233 -38
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +584 -2
  10. package/dist/index.d.ts +584 -2
  11. package/dist/index.js +111 -3
  12. package/dist/mcp.cjs +1215 -365
  13. package/dist/mcp.cjs.map +1 -1
  14. package/dist/mcp.js +1209 -365
  15. package/dist/mcp.js.map +1 -1
  16. package/package.json +20 -9
  17. package/src/web/inline-app.ts +867 -0
  18. package/src/web/tsconfig.json +9 -0
  19. package/templates/welcome.atelier +67 -0
  20. package/university/content/notes/N-atel-001-first-render.md +114 -0
  21. package/university/content/notes/N-atel-001-install-and-launch.md +84 -0
  22. package/university/content/notes/N-atel-001-what-is-atelier.md +51 -0
  23. package/university/content/notes/N-atel-101-easings.md +97 -0
  24. package/university/content/notes/N-atel-101-layers.md +106 -0
  25. package/university/content/notes/N-atel-101-states-and-deltas.md +94 -0
  26. package/university/content/notes/N-atel-101-the-atelier-format.md +72 -0
  27. package/university/content/notes/N-atel-201-authoring-tools.md +141 -0
  28. package/university/content/notes/N-atel-201-mcp-overview.md +86 -0
  29. package/university/content/notes/N-atel-201-patterns.md +108 -0
  30. package/university/content/notes/N-atel-201-visual-and-effects.md +125 -0
  31. package/university/content/notes/N-atel-301-composition-and-overlays.md +141 -0
  32. package/university/content/notes/N-atel-301-effects.md +136 -0
  33. package/university/content/notes/N-atel-301-images-and-video.md +126 -0
  34. package/university/content/notes/N-atel-301-shapes-and-text.md +118 -0
  35. package/university/content/notes/N-atel-401-hierarchical-states.md +71 -0
  36. package/university/content/notes/N-atel-401-motion-deep-dive.md +106 -0
  37. package/university/content/notes/N-atel-401-presets-and-templates.md +98 -0
  38. package/university/content/notes/N-atel-401-transitions.md +94 -0
  39. package/university/content/notes/N-atel-501-detected-vs-user-edited.md +76 -0
  40. package/university/content/notes/N-atel-501-layer-tag-isolation.md +62 -0
  41. package/university/content/notes/N-atel-501-silence-trim.md +98 -0
  42. package/university/content/notes/N-atel-501-transcribe-and-captions.md +98 -0
  43. package/university/content/notes/N-atel-601-carousel.md +71 -0
  44. package/university/content/notes/N-atel-601-overlay-rules.md +96 -0
  45. package/university/content/notes/N-atel-601-recipe-tools-and-apply.md +84 -0
  46. package/university/content/notes/N-atel-601-studio-recipe.md +103 -0
  47. package/university/content/notes/N-atel-701-choosing-output.md +68 -0
  48. package/university/content/notes/N-atel-701-png-and-frames.md +84 -0
  49. package/university/content/notes/N-atel-701-vector.md +85 -0
  50. package/university/content/notes/N-atel-701-video.md +88 -0
  51. package/university/content/notes/N-atel-801-editing-surface.md +69 -0
  52. package/university/content/notes/N-atel-801-live-bridge.md +84 -0
  53. package/university/content/notes/N-atel-801-studio-app.md +72 -0
  54. package/university/content/notes/N-atel-801-symbiotic-loop.md +56 -0
  55. package/university/content/paths/LP-atel-001.yaml +21 -0
  56. package/university/content/paths/LP-atel-101.yaml +22 -0
  57. package/university/content/paths/LP-atel-201.yaml +23 -0
  58. package/university/content/paths/LP-atel-301.yaml +22 -0
  59. package/university/content/paths/LP-atel-401.yaml +22 -0
  60. package/university/content/paths/LP-atel-501.yaml +22 -0
  61. package/university/content/paths/LP-atel-601.yaml +22 -0
  62. package/university/content/paths/LP-atel-701.yaml +22 -0
  63. package/university/content/paths/LP-atel-801.yaml +22 -0
  64. package/university/content/quizzes/Q-atel-001-orientation.yaml +66 -0
  65. package/university/content/quizzes/Q-atel-101-document-model.yaml +66 -0
  66. package/university/content/quizzes/Q-atel-201-mcp-authoring.yaml +66 -0
  67. package/university/content/quizzes/Q-atel-301-visual-system.yaml +66 -0
  68. package/university/content/quizzes/Q-atel-401-state-machines.yaml +66 -0
  69. package/university/content/quizzes/Q-atel-501-video-pipeline.yaml +66 -0
  70. package/university/content/quizzes/Q-atel-601-recipes.yaml +66 -0
  71. package/university/content/quizzes/Q-atel-701-export.yaml +66 -0
  72. package/university/content/quizzes/Q-atel-801-studio-loop.yaml +66 -0
  73. package/university/index.yaml +720 -0
  74. package/university/pack.yaml +21 -0
  75. package/dist/chunk-JV7RGETS.js +0 -2292
  76. 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.