0033. 0033 — A frame is its size on the canvas
Status: accepted 2026-11-02. Extends 0032.
Context
Section titled “Context”0032 made a free element read in canvas px and kept drawn frames at two doc px per canvas px “for export”. The inspector then read a frame and its layers in doc px while X/Y were canvas px: a 700×460 frame showed Frame size 1400×920, a 160 px rect W 320, a radius of 12 drew 6 px. Frames from a preset format are worse still: a 1920×1080 doc in a 420 px node. Doc px per canvas px was never one number: drawn 2, hand-resized 1 (a handle drag re-stages the doc at the node size), preset ~4.6.
Figma: a frame’s size is design units; resolution is chosen at export (1×, 2×, 3×…).
Decision
Section titled “Decision”- Design units are canvas px. A frame’s size is its node box.
scaleOf(doc, node)insrc/lib/motion/units.tsis canvas px per doc px;spaceOf(doc, scale)is the stage every px field of the inspector converts through (frame surface, layers, multi-selection, camera, animate-to). It is the single conversion point of 0032, now fed with the node box for every node, not only free elements. Old docs are not rewritten. - New frames are drawn one doc px per canvas px, as elements and hand-resized frames already
were (
DOC_PX_PER_CANVAS_PX,draw-create.ts). For them the factor is 1, so agents, MCP and the inspector read the same numbers. - Frame size edits the node. W, H and presets resize the node through the same path as a
handle drag (
NodePlace.resize→onResize), so the doc follows at one px per canvas px. - Image export scale is of the design size.
rowSizetakes the design size (doc × docScale); the page or SVG is drawn atrow scale × docScale.docScalecomes from the node record on the server (docScaleOf,editor.ts) forexport_selectionand the still export. The dialogs,feega canvas exportand the still dialog start at 2×, so a legacy 2× frame exports exactly as before and a new 1× frame as sharp. - Video keeps its resolution. MP4 already picks a short side (720p–4K) independent of the
doc; the browser render now scales up to it like the farm (
exportSize=outputSize, no cap at the doc size), so 1× docs render as sharp as 2× ones did. - Resizing a frame keeps what is inside it. A resize (handle or typed) keeps the doc px per
canvas px the frame had (
densityKept,custom-size.ts), applies constraints, and holds type size, strokes and corner radius in px (heldPixelsfor every frame, not only stills, plusheldRadii: roundness is a share of a layer’s short side, so a stretched surface grew from 12 to 92 px). Typing W on an auto-width text makes it fixed width (resizedTexts, now run on every inspector edit). Wrapping a text in a stack keeps its sizing (TEXT_ITEM, shared with the Figma import). - A library text style is px. The project library holds
sizePx; a doc holds the share of its own short side.inLibrary/inDoc(style-library.ts) are the only conversions, so a 16 px style is 16 px in a phone and in a 52 px button. Legacy library entries withsizekeep their share until edited. - The motion agent and MCP speak canvas px too (amended 2026-11-03). Where tools run
(
turn.ts,tool-run.ts,motionSummary) the node box gives the scale (docScaleOf,doc-scale.ts) andtoolSpace(doc, scale)(tool-space.ts) is the one boundary: itsframefeeds every units conversion,toDoc/toCanvasthe raw px (arrange offsets, guides, depth,set_canvassizes),layoutIn/layoutOutthe stack gap, padding and fixed tracks.export_imagescale 1 is one image px per canvas px. The timeline value fields and project text styles convert through the same scale.
Rejected
Section titled “Rejected”- Keeping drawn frames at 2× and converting at every agent tool: ~30 conversion sites in
motion-tools.ts. The amendment of 8 converts at one boundary instead: the sites take thetoolSpaceframe, no per-tool condition. - Rescaling the doc for the tool run and back: custom component CSS and stored doc px (blur, depth, gaps) would need a full uniform rescale, lossy on every save.
- Rescaling old docs to 1× on open (same reason as 0032).
- A separate 1×/2× control for MP4: the resolution control already is the video’s scale.
- Defaulting export to each frame’s native density: one more rule; 2× covers the frames people draw. A preset frame denser than 2× exports smaller at 2× than its old 1×; pick 4× or a width.