# Composing decks in the wiredeck studio

A deck is a folder: deck.json, assets/ and fonts/. Every slide is a layout with typed slots (the text) plus an optional composition of layers (the images and films). Text is never positioned by hand; layers always are.

## The canvas
Every slide is 1920 x 1080 canvas pixels. x grows right, y grows down. Layers may extend past the edges to bleed off the slide.

## Revisions
Every read returns a revision. Every write must pass the revision it was based on and returns the new one. If the file changed in between (someone edited it, or another write landed), the write is refused: read again and reapply. Never guess a revision.

## Layouts and their slots
Every layout has a title. Write labels and tags in sentence case; the renderer never capitalises or tracks them.
cover: title, lede?. The opening slide: a very large title over a short paragraph, centred vertically on the left.
feature: eyebrow? (a short tag such as "Available"), title, subtitle? (an italic line under the title), lede? (a paragraph), facts? [{label (max 24 chars, such as "What"), value, tone: "ink" | "mute"}], link? (a short address, no scheme). The text sits in a column from x 120 to x 1000. Put the composition to the right, from about x 1080, and let it bleed off the right edge if that suits it.
statement: title, lede? (italic), notes? [{label, value}] (up to 3, in a row along the foot). A section break or a closing thought; usually no layers.
points: eyebrow?, title, lede?, points [{label, body}] (2 to 6), numbered (true puts 01, 02... beside each). Title and lede on the left, the points in a ruled list on the right. Usually no layers.
sequence: title, steps [{asset, caption}] (2 to 6 images read in order), frame: "phone" | "plain". Phone frames sit in one row; plain frames take the first image's shape and wrap into two rows past three. Laid out for you.
figure: title, image (an image asset). One wide image, as large as the slide allows, with the title above it.
film: title, film (a video asset). The frame is laid out for you; a play button starts it.

Inline text in titles, subtitles, ledes, fact values and points: *italic*, [label](https://...), and line breaks. Nothing else.

## Layers
{ kind: "image" | "film" | "loop", asset, box: {x, y, w, h}, crop?: {x, y, w, h}, clip: "box" | "rounded" | "circle", radius (default 16), border?: {width, color}, outline?: {width, color}, shadows: [{x, y, blur, spread, color, inset}], fill?, opacity? }
- Stacking order is array order: later layers sit in front. Use reorder_layers to change it.
- crop is in the asset's own pixels: the part of the asset shown, stretched to fill the box. Leave it out to cover the box, centred. Keep the crop's aspect ratio equal to the box's.
- A magnifying lens is a second layer showing the same asset with a small crop, clip "circle".
- Colours are theme names (ground, ink, mute, faint, hairline, accent) or hex like #0000008C.
- film: a video with sound, started by the presenter. loop: a muted video that plays continuously.
- Typical treatment: radius 16, shadows [{y: 40, blur: 90, color: "#0000008C"}], border {width: 1, color: "hairline"}.

## Errors
Failed calls return { ok: false, code, error, is_retriable, recovery_hint }. STALE_REVISION carries currentRevision: read again and reapply. VALIDATION carries issues with the path of each bad field. NOT_FOUND means an ID or position no longer exists: call get_deck. STUDIO_UNREACHABLE means start the studio with pnpm studio <deck folder>.

## Workflow
1. get_deck for the outline, IDs and revision.
2. add_asset for each local image or film. Videos get a poster frame automatically.
3. add_slide with the layout and slots.
4. write_layers to compose, in small steps.
5. Before delete_slides, delete_layers or write_layers with mode "replace", run it with dryRun: true and check what it would remove.
6. screenshot after every composition change and look at it. It also reports text that overflows its slot: shorten the text, never shrink it.
7. export_pdf when asked for a PDF.
8. Publish only when the person asks: publish_deck runs as a dry run first; show them what it would upload, then call it with dryRun: false and confirm: true. get_publish_status gives the share link. Passwords stay in the deck folder and are never returned.
