@a-company/atelier 0.36.0 → 0.38.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-JPZ4F4PW.js → chunk-3ARBOSWY.js} +64 -5
- package/dist/chunk-3ARBOSWY.js.map +1 -0
- package/dist/cli.js +11469 -413
- package/dist/cli.js.map +1 -1
- package/dist/{dist-M67UZGFQ.js → dist-3YQK6PI6.js} +2 -2
- package/dist/index.cjs +3193 -227
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +701 -8
- package/dist/index.d.ts +701 -8
- package/dist/index.js +7237 -72
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +2898 -507
- package/dist/mcp.js.map +1 -1
- package/package.json +14 -9
- package/src/web/inline-app.ts +55 -4
- package/src/web/timeline-state-types.ts +28 -0
- package/src/web/timeline-view.test.ts +99 -0
- package/src/web/timeline-view.ts +339 -0
- package/src/web/workspace-app.ts +3146 -0
- package/templates/workspace/.claude/agents/atelier-iris.md +75 -0
- package/templates/workspace/.claude/agents/atelier-lux.md +67 -0
- package/templates/workspace/.claude/agents/atelier-quill.md +61 -0
- package/templates/workspace/.gitignore +30 -0
- package/templates/workspace/.paradigm/personas/_shared/cascade-merge.md +172 -0
- package/templates/workspace/CLAUDE.md +93 -0
- package/templates/workspace/README.md +75 -0
- package/templates/workspace/SETUP.md +127 -0
- package/templates/workspace/_brand/.atelier-brand.yaml +34 -0
- package/templates/workspace/_brand/DESIGN.md +56 -0
- package/templates/workspace/_brand/SCRIPT.md +41 -0
- package/templates/workspace/_brand/STORYBOARD.md +33 -0
- package/templates/workspace/_packs/README.md +54 -0
- package/templates/workspace/projects/README.md +49 -0
- package/templates/workspace/workspace.atelier +22 -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-5QQESXI6.js +0 -4432
- package/dist/chunk-5QQESXI6.js.map +0 -1
- package/dist/chunk-JPZ4F4PW.js.map +0 -1
- package/dist/cli.cjs +0 -6313
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/mcp.cjs +0 -5462
- package/dist/mcp.cjs.map +0 -1
- /package/dist/{dist-M67UZGFQ.js.map → dist-3YQK6PI6.js.map} +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-101-easings
|
|
3
|
+
title: Easings — linear, cubic-bezier, presets, spring, step
|
|
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
|
+
- easings
|
|
13
|
+
- motion
|
|
14
|
+
difficulty: beginner
|
|
15
|
+
estimatedMinutes: 4
|
|
16
|
+
prerequisites:
|
|
17
|
+
- N-atel-101-states-and-deltas
|
|
18
|
+
summary: Five easing kinds in Atelier. What each is, when to use it, and the one rule that prevents 90% of bad motion (don't use `linear` for organic movement).
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What an easing does
|
|
22
|
+
|
|
23
|
+
An easing is a function `f(t: 0..1) → 0..1` that maps "fraction of time elapsed" to "fraction of value progress." `f(0) = 0`, `f(1) = 1`. What happens in between is the personality of the motion.
|
|
24
|
+
|
|
25
|
+
Atelier supports five kinds:
|
|
26
|
+
|
|
27
|
+
## 1. `linear`
|
|
28
|
+
|
|
29
|
+
`f(t) = t`. Constant rate. Use for: progress bars, scrubbing, opacity crossfades where the perceived speed matters. **Do not use** for object motion — it looks robotic. Most "feels wrong" motion in the wild is `linear` doing the wrong job.
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
easing: linear
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 2. Cubic-Bezier — `cubic-bezier(x1, y1, x2, y2)`
|
|
36
|
+
|
|
37
|
+
The CSS-style four-control-point bezier. Full control over the shape. This is what every preset below desugars to.
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
easing: cubic-bezier(0.4, 0.0, 0.2, 1.0) # Material "standard"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If you've used CSS transitions, this is the same function.
|
|
44
|
+
|
|
45
|
+
## 3. Ease presets
|
|
46
|
+
|
|
47
|
+
Named shortcuts to common bezier curves. Use these instead of memorizing coefficients.
|
|
48
|
+
|
|
49
|
+
| Preset | Bezier | Use for |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `ease-in` | `(0.4, 0, 1, 1)` | Object accelerating off-screen |
|
|
52
|
+
| `ease-out` | `(0, 0, 0.2, 1)` | Object decelerating on entrance (most UI entrance animation) |
|
|
53
|
+
| `ease-in-out` | `(0.4, 0, 0.2, 1)` | Crossfades, value-to-value transitions where both endpoints are visible |
|
|
54
|
+
| `ease` | `(0.25, 0.1, 0.25, 1)` | The CSS default — generic "feels OK" |
|
|
55
|
+
|
|
56
|
+
Default for most UI motion: `ease-out` for entrances, `ease-in` for exits.
|
|
57
|
+
|
|
58
|
+
## 4. Spring physics — `spring({ stiffness, damping, mass? })`
|
|
59
|
+
|
|
60
|
+
Physics-based. Produces overshoot and oscillation that no bezier can match. Use for: anything that should feel tactile — toggles, drag-release, dismissals.
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
easing:
|
|
64
|
+
type: spring
|
|
65
|
+
stiffness: 170
|
|
66
|
+
damping: 20
|
|
67
|
+
mass: 1
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Stiffness controls how aggressively the spring pulls toward the target. Damping controls how quickly oscillation dies out. Atelier solves the spring numerically per-frame; the same `startFrame`–`endFrame` window still applies (the spring is clipped at `endFrame` and held at the resolved value after).
|
|
71
|
+
|
|
72
|
+
When in doubt: `stiffness: 170, damping: 26` for a clean snap; `stiffness: 100, damping: 10` for visible oscillation.
|
|
73
|
+
|
|
74
|
+
## 5. Step — `step(count)` or `step(count, position)`
|
|
75
|
+
|
|
76
|
+
Discrete jumps. `step(4)` produces four equal jumps from `from` to `to`. Use for: spritesheet frame indexing (jump through frames at 12fps inside a 60fps composition), digital-readout counters, deliberate stop-motion feel.
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
easing:
|
|
80
|
+
type: step
|
|
81
|
+
count: 4
|
|
82
|
+
position: start # 'start' | 'end' | 'jump-none' | 'jump-both'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## The rule that prevents most bad motion
|
|
86
|
+
|
|
87
|
+
> If a real physical object would do it, use spring or ease-out.
|
|
88
|
+
> If a UI element is reporting progress, use linear.
|
|
89
|
+
> Almost nothing else should be linear.
|
|
90
|
+
|
|
91
|
+
Easings are why motion feels "designed" or "wrong." The engine doesn't care which you pick — the renderer interpolates whatever `f(t)` returns. But your viewer does care, every time.
|
|
92
|
+
|
|
93
|
+
## Inspecting an easing
|
|
94
|
+
|
|
95
|
+
The studio's easing-curve-editor (in PropertyPanel when you select a delta) shows the bezier curve, lets you drag control points, and previews the shape against the timeline. For springs, it samples the solver and plots the actual oscillation. There is no "easings preview file" — every delta you author can be inspected and tuned in place.
|
|
96
|
+
|
|
97
|
+
That closes ATEL-101. You now know enough to read any `.atelier` document and predict what it will render. ATEL-201 covers the MCP tool surface — how an agent builds documents like this without ever touching YAML.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-101-layers
|
|
3
|
+
title: Layer anatomy
|
|
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
|
+
- layers
|
|
13
|
+
- format
|
|
14
|
+
difficulty: beginner
|
|
15
|
+
estimatedMinutes: 5
|
|
16
|
+
prerequisites:
|
|
17
|
+
- N-atel-101-the-atelier-format
|
|
18
|
+
summary: Every field on the `Layer` type, what it does, what's required vs optional, and which fields are animatable through deltas.
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## The minimum layer
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
- id: title
|
|
25
|
+
visual:
|
|
26
|
+
type: text
|
|
27
|
+
content: Hello
|
|
28
|
+
style:
|
|
29
|
+
fontFamily: Inter, system-ui, sans-serif
|
|
30
|
+
fontSize: 48
|
|
31
|
+
frame:
|
|
32
|
+
x: 400
|
|
33
|
+
y: 300
|
|
34
|
+
bounds:
|
|
35
|
+
width: 600
|
|
36
|
+
height: 80
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Four required fields: `id`, `visual`, `frame`, `bounds`. The rest are optional and default to sensible values.
|
|
40
|
+
|
|
41
|
+
## The six visual types
|
|
42
|
+
|
|
43
|
+
The `visual` field is a discriminated union on `type`:
|
|
44
|
+
|
|
45
|
+
| `type` | Renders | Required sub-fields |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `shape` | A geometric shape — rect, ellipse, polygon, path | `shape`, optional `fill` and `stroke` |
|
|
48
|
+
| `text` | A string with typography | `content`, `style` (fontFamily required) |
|
|
49
|
+
| `image` | A static image asset | `assetId` (and optional `src`, `sourceRect`, `spritesheet`) |
|
|
50
|
+
| `video` | A video clip | `assetId`, optional `sourceOffset`, `sourceEnd`, `playbackRate` |
|
|
51
|
+
| `group` | A container for child layers (via `parentId`) | none — just a container |
|
|
52
|
+
| `ref` | A reference to another `.atelier` file rendered as a sub-document | `src`, optional `state` and `frame` |
|
|
53
|
+
|
|
54
|
+
## Position, size, and anchor
|
|
55
|
+
|
|
56
|
+
- **`frame: { x, y }`** — position of the anchor point in canvas pixels.
|
|
57
|
+
- **`bounds: { width, height }`** — the layer's box.
|
|
58
|
+
- **`anchorPoint: { x, y }`** — normalized 0–1 coordinate inside the bounds that `frame` positions. `{x: 0.5, y: 0.5}` centers the layer on `frame`. `{x: 0, y: 0}` puts the top-left at `frame`. Default is `{x: 0, y: 0}`.
|
|
59
|
+
|
|
60
|
+
The anchor also defines the pivot for `rotation` and `scale`.
|
|
61
|
+
|
|
62
|
+
## Animatable layer properties
|
|
63
|
+
|
|
64
|
+
These can appear in deltas (`property: <name>`):
|
|
65
|
+
|
|
66
|
+
| Property | Type | Default |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `opacity` | number 0–1 | 1 |
|
|
69
|
+
| `rotation` | degrees | 0 |
|
|
70
|
+
| `scale` | `{ x, y }` | `{1, 1}` |
|
|
71
|
+
| `frame.x`, `frame.y` | number | as authored |
|
|
72
|
+
| `bounds.width`, `bounds.height` | number | as authored |
|
|
73
|
+
| `tint.amount` | 0–1 (color tint strength) | 0 |
|
|
74
|
+
| Any color, gradient stop, stroke width, shadow blur on the visual or its style | various | — |
|
|
75
|
+
|
|
76
|
+
The full animatable list is 27 properties. The schema enforces that a delta's `property` path is reachable on the named layer's visual.
|
|
77
|
+
|
|
78
|
+
## Non-animatable structural fields
|
|
79
|
+
|
|
80
|
+
| Field | Purpose |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `id` | Unique identifier |
|
|
83
|
+
| `tags` | Namespace markers (used by pipelines for layer-tag isolation) |
|
|
84
|
+
| `description` | Human-readable note, kept out of render |
|
|
85
|
+
| `parentId` | Parents this layer to another for transform inheritance |
|
|
86
|
+
| `visible` | Hard on/off (rendered if `true` or absent) |
|
|
87
|
+
| `blendMode` | Canvas composite operation |
|
|
88
|
+
| `clipPath` | A Shape that restricts rendering to its interior |
|
|
89
|
+
| `motionPath` | A path the layer follows over time, with optional auto-rotate |
|
|
90
|
+
| `interactions` | Event-driven trigger-action bindings (e.g. hover → state transition) |
|
|
91
|
+
|
|
92
|
+
## Parent-child and transform inheritance
|
|
93
|
+
|
|
94
|
+
Setting `parentId` on a layer makes its `frame`, `rotation`, and `scale` relative to the parent's transformed coordinate space. A `group` visual has no rendered output of its own — it exists purely to provide a parent transform. Use groups when you want to translate or rotate a cluster of layers as one unit.
|
|
95
|
+
|
|
96
|
+
## Tags: the pipeline contract
|
|
97
|
+
|
|
98
|
+
Tags are how the silence-trim, caption, and overlay pipelines find layers they own. Each pipeline writes a tag namespace:
|
|
99
|
+
|
|
100
|
+
- `silence-trim` — clip layers produced by `atelier trim`
|
|
101
|
+
- `caption`, `caption-word` — text layers produced by `atelier captions regenerate`
|
|
102
|
+
- `overlay` — handle / page-number / recipe-driven decorative layers
|
|
103
|
+
|
|
104
|
+
The invariant: re-running a pipeline drops only its own tagged layers and replaces them. Layers without that tag (user-authored, or owned by another pipeline) are never touched. This is what makes the human↔agent symbiotic loop safe (ATEL-501 covers this in depth).
|
|
105
|
+
|
|
106
|
+
Next: states and deltas.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: N-atel-101-states-and-deltas
|
|
3
|
+
title: States, deltas, and `resolveFrame`
|
|
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
|
+
- states
|
|
13
|
+
- deltas
|
|
14
|
+
- resolve-frame
|
|
15
|
+
difficulty: beginner
|
|
16
|
+
estimatedMinutes: 5
|
|
17
|
+
prerequisites:
|
|
18
|
+
- N-atel-101-layers
|
|
19
|
+
summary: A state is a named span of frames. A delta animates one property of one layer over a sub-range of that span. `resolveFrame(doc, stateName, frame)` is the function the renderer calls — knowing its shape demystifies everything else.
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## A state is a named span
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
states:
|
|
26
|
+
default:
|
|
27
|
+
duration: 60
|
|
28
|
+
deltas:
|
|
29
|
+
- layer: title
|
|
30
|
+
property: opacity
|
|
31
|
+
from: 0
|
|
32
|
+
to: 1
|
|
33
|
+
startFrame: 0
|
|
34
|
+
endFrame: 30
|
|
35
|
+
easing: ease-out
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`default` is the state name. `duration: 60` means this state runs for 60 frames (one second at 60fps). Frames are 0-indexed, so this state spans `0..59` inclusive. The renderer plays through the state and either loops, transitions to another state, or stops, depending on configuration.
|
|
39
|
+
|
|
40
|
+
A document always has at least one state. Most documents have only one. Multi-state documents are the foundation of interactive UI motion (`hover`, `active`, `pressed`) and segmented narratives.
|
|
41
|
+
|
|
42
|
+
## A delta animates one property
|
|
43
|
+
|
|
44
|
+
Every field on a delta exists for a reason:
|
|
45
|
+
|
|
46
|
+
| Field | Meaning |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `layer` | Which layer (by id) this delta animates |
|
|
49
|
+
| `property` | Which animatable property (e.g. `opacity`, `frame.x`, `style.color`) |
|
|
50
|
+
| `from` | Starting value at `startFrame` |
|
|
51
|
+
| `to` | Ending value at `endFrame` |
|
|
52
|
+
| `startFrame` | Frame within the state where this delta begins |
|
|
53
|
+
| `endFrame` | Frame within the state where this delta ends |
|
|
54
|
+
| `easing` | How the value interpolates between `from` and `to` |
|
|
55
|
+
| `expression` | Optional override — a function of `t` (0..1) returning the interpolated value, for non-trivial animations |
|
|
56
|
+
|
|
57
|
+
Outside `[startFrame, endFrame]`, the property holds its boundary value (clamped). Multiple deltas on the same `layer` + `property` are rejected by the schema's no-overlap gate — overlapping motion is almost always a bug; if you need composition, use two layers or a `group`.
|
|
58
|
+
|
|
59
|
+
## `resolveFrame` is the whole engine in one function
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const scene = resolveFrame(doc, stateName, frameNumber);
|
|
63
|
+
// scene is { layers: ResolvedLayer[] } — every property concrete, no deltas
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
What it does, in order:
|
|
67
|
+
|
|
68
|
+
1. Look up the state by name. If it doesn't exist, throw.
|
|
69
|
+
2. For each layer in `doc.layers`, start with its authored values.
|
|
70
|
+
3. For each delta in `state.deltas`:
|
|
71
|
+
- If `frameNumber < startFrame`, the layer's property stays at `from`.
|
|
72
|
+
- If `frameNumber > endFrame`, the layer's property stays at `to`.
|
|
73
|
+
- If between: compute `t = (frameNumber - startFrame) / (endFrame - startFrame)`, run `t` through the easing function (returning `t'`), then interpolate `from + (to - from) * t'`.
|
|
74
|
+
4. Apply parent transforms (if a layer has `parentId`), motion paths, clip paths, tints.
|
|
75
|
+
5. Return the resolved scene — every layer now has concrete property values ready for the renderer.
|
|
76
|
+
|
|
77
|
+
The renderer (canvas, SVG export, Lottie export, video exporter) all consume the same `ResolvedLayer[]` output. Adding a new renderer is "consume one frame at a time"; adding a new animatable property is "add to the interpolation switch."
|
|
78
|
+
|
|
79
|
+
## Hierarchical states and transitions
|
|
80
|
+
|
|
81
|
+
States can declare a `parent` (extending another state's deltas) and a `transitions` block (named transitions from one state to another with their own duration, easing, and target deltas). This is the foundation of interactive UI motion — `hover` extends `default` with extra deltas, `pressed` transitions from `hover` with a 200ms ease-out. Full coverage in ATEL-401 (State Machines & Motion).
|
|
82
|
+
|
|
83
|
+
## Inspecting a frame from the CLI
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
atelier preview hello.atelier --frame 15
|
|
87
|
+
atelier preview hello.atelier --frame 15 --state hover
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The output is the same `ResolvedLayer[]` array `resolveFrame` produces — perfect for debugging "what does the engine actually think this scene looks like at frame N?"
|
|
91
|
+
|
|
92
|
+
For multi-frame inspection, `atelier_batch_preview` (MCP) or `atelier diff_frames` (CLI) compare two frames side by side.
|
|
93
|
+
|
|
94
|
+
Next: easings.
|
|
@@ -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.
|