diff --git a/docs/wg/feat-paragraph/index.md b/docs/wg/feat-paragraph/index.md
index c158674a87..750f4bb011 100644
--- a/docs/wg/feat-paragraph/index.md
+++ b/docs/wg/feat-paragraph/index.md
@@ -1,14 +1,33 @@
---
-title: Paragraph Feature Roadmap
+title: Paragraph and Text Layout
+description: "Specifications, focused studies, and implementation coverage for paragraph and typography behavior."
tags:
- internal
- wg
- typography
- text
- fonts
+format: md
---
-# Paragraph Feature Roadmap
+# Paragraph and Text Layout
+
+## Specifications
+
+- [Universal Shaped Text Layout](./text-layout) defines the canonical
+ resolution contract shared by geometry-sensitive consumers.
+
+## Focused studies
+
+- [Font fallback](./impl-font-fallback)
+- [Italic and oblique](./impl-italic)
+- [Optical size automation](./impl-opsz-auto)
+- [Emoji placeholder rendering](./impl-emoji-placeholder)
+
+## Coverage inventory
+
+The checklist below records implementation coverage. It does not define
+measurement, shaping, line, glyph, or editing geometry semantics.
**Core / Modeling**
@@ -65,7 +84,7 @@ tags:
- [x] text-decoration-thickness (only supports %)
- [ ] text-decoration-skip-ink - https://github.com/rust-skia/rust-skia/issues/1187
- [ ] known limitations https://github.com/gridaco/grida/issues/416
-- [x] [Variable axes](https://github.com/gridaco/grida/blob/canary/docs/reference/open-type-variable-axes.md)
+- [x] [Variable axes](../../reference/open-type-variable-axes)
- [x] `wght`
- [x] `wdth`
- [x] `slnt`
@@ -84,7 +103,7 @@ tags:
- [x] `YTAS`
- [x] `YTDE`
- [x] `YTFI`
-- [x] [open type featuers](https://github.com/gridaco/grida/blob/canary/docs/reference/open-type-features.md)
+- [x] [OpenType features](../../reference/open-type-features)
**Text Editor**
diff --git a/docs/wg/feat-paragraph/text-layout.md b/docs/wg/feat-paragraph/text-layout.md
new file mode 100644
index 0000000000..6c5ed59b4a
--- /dev/null
+++ b/docs/wg/feat-paragraph/text-layout.md
@@ -0,0 +1,601 @@
+---
+title: Universal Shaped Text Layout
+description: "Open RFD for one versioned text-resolution contract shared by measurement, painting, hit testing, editing, and export."
+keywords:
+ - text layout
+ - text shaping
+ - typography
+ - glyph runs
+ - line breaking
+ - rich text
+tags:
+ - internal
+ - wg
+ - typography
+ - text
+ - fonts
+ - layout
+format: md
+---
+
+# Universal Shaped Text Layout
+
+**Status:** Open RFD.
+
+## Thesis
+
+Text resolution must happen once.
+
+Attributed Unicode text, paragraph intent, box constraints, an explicit font
+and language environment, and a text-oracle version resolve to one immutable
+text-layout artifact. Measurement, painting, hit testing, editing geometry,
+damage calculation, and faithful export all consume that same artifact. None
+of those consumers may independently estimate widths, choose line breaks,
+select fallback fonts, or reshape the text.
+
+This is the **single text-resolution contract**:
+
+```text
+attributed text
++ paragraph properties
++ box constraints
++ explicit resolution environment
++ text-oracle version
+ -> resolved text layout | typed resolution failure
+```
+
+The contract does not require every host to use one global typography
+implementation forever. It requires every resolved result to identify the
+oracle and environment that produced it, and every consumer of that result to
+agree on its geometry.
+
+## Problem
+
+Text is not a sequence of independently sized characters. Font selection,
+shaping, bidirectional ordering, glyph substitution, cluster formation, line
+breaking, alignment, and truncation jointly determine its geometry. A width
+estimate made before shaping is therefore not a weaker version of the final
+answer; it may be a different answer.
+
+If measurement and painting resolve text separately, they can disagree on:
+
+- which font renders a source range;
+- how source characters combine into glyphs and clusters;
+- glyph advances and offsets;
+- legal break opportunities and final line breaks;
+- line metrics and baseline positions;
+- the width and height reported to surrounding layout;
+- caret stops, hit results, and selection geometry;
+- truncation and the placement of an ellipsis or other marker;
+- logical bounds versus the bounds of visible ink.
+
+These disagreements compound. A different measured width changes a parent
+layout; that new layout changes wrapping; the new wrapping changes height,
+paint bounds, hit testing, and export. The system must remove the split at its
+source rather than make each downstream consumer imitate the others.
+
+## Meaning of universal
+
+“Universal” describes **consumption**, not unlimited feature support.
+
+- One resolved artifact is universal across all geometry-sensitive consumers
+ of one text node.
+- The contract is independent of a particular scene language, renderer,
+ operating system, or programming language.
+- Script, writing-mode, feature, and font coverage may grow by oracle version.
+ Unsupported input fails explicitly; it does not receive approximate layout.
+- A different constraint, font manifest, language policy, or oracle version
+ produces a different resolution. It is not another view of the old result.
+
+Universal does not mean that rasterized pixels must be identical across every
+graphics backend. It means the selected fonts, glyphs, positions, lines,
+baselines, mappings, and base bounds are already fixed before rasterization.
+
+## Vocabulary
+
+| Term | Meaning |
+| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| **Source text** | The exact Unicode content presented to resolution, addressed by UTF-8 byte offsets at Unicode-scalar boundaries. |
+| **Shaping text** | The derived character sequence presented to shaping after any explicit source transformation, with a mapping back to source. |
+| **Attributed text** | Source text plus complete, non-overlapping style coverage and stable associations to any paint-only run data. |
+| **Paragraph intent** | Direction, writing mode, alignment, spacing, wrapping, line limit, truncation, and other line-level choices. |
+| **Resolution environment** | The complete external facts needed to resolve text, including exact font resources, fallback policy, language data, and scale. |
+| **Text oracle** | The versioned shaping, segmentation, bidirectional, breaking, metrics, and numeric policy that turns the inputs into geometry. |
+| **Shaping cluster** | A unit emitted by shaping that relates a shaping-text range to zero or more positioned glyphs and back to authored source. |
+| **Caret stop** | A legal editing boundary with a visual position and affinity; it is not assumed to exist at every UTF-8 or glyph boundary. |
+| **Logical bounds** | Typographic layout extents, including advances and line boxes even where no ink is drawn. |
+| **Ink bounds** | The tight base drawing extents of resolved glyphs and layout-owned marks before node paints, strokes, filters, or effects. |
+| **Resolved text layout** | The immutable, inspectable result described by this RFD. |
+
+A grapheme cluster, a shaping cluster, a glyph, and a caret stop are distinct
+concepts. They often align for simple Latin text and often do not align for
+ligatures, combining sequences, emoji sequences, or complex scripts. The
+resolved artifact must not collapse them into one index space.
+
+## Input contract
+
+Resolution begins only after authoring syntax has been parsed and defaults
+have been made complete. An authoring format may expose a compact subset of
+these inputs, but it must not make omitted values depend on undeclared host
+state.
+
+### Attributed source
+
+The attributed source supplies:
+
+1. the exact source string;
+2. complete style coverage of that string;
+3. character-boundary run ranges in UTF-8 byte coordinates; and
+4. any explicit language, script, direction, or shaping overrides attached to
+ those ranges.
+
+For non-empty text, style ranges are ordered, contiguous, non-overlapping, and
+cover the complete source string. An empty string carries a complete default
+style without inventing a source character.
+
+Layout-affecting style includes at least the requested font families or faces,
+size, weight, width, posture, variable axes, OpenType features, optical sizing,
+letter spacing, word spacing, baseline shift, and language or script hints
+when exposed. Paint-only values may remain associated with source ranges, but
+they do not change shaping or invalidate layout unless their semantics alter
+geometry.
+
+Authored source is not Unicode-normalized by default. If a source feature
+transforms text before shaping—for example case transformation—the
+transformation policy is an explicit input and the result retains a complete
+mapping back to the original UTF-8 source ranges.
+
+### Paragraph intent
+
+Paragraph intent is complete before resolution. It includes, when supported:
+
+- base direction and writing mode;
+- horizontal and vertical alignment within the assigned box;
+- line-height policy and paragraph spacing;
+- soft-wrap and legal-break policy;
+- tab and whitespace handling;
+- locale-sensitive line or word breaking;
+- hyphenation policy and dictionary identity;
+- a maximum line count; and
+- overflow and truncation-marker policy.
+
+Explicit source line breaks are source content. A soft break chosen by the
+oracle is derived geometry. The result distinguishes the two.
+
+### Box constraints
+
+Constraints describe the available inline and block extents in logical units.
+Each axis may be unconstrained, exact, or bounded by a finite minimum and
+maximum. Values are finite and non-negative, and minima do not exceed maxima.
+
+The constraint input and the final assigned text box are both retained in the
+result. The assigned box is distinct from the logical content bounds and ink
+bounds. Fixed block extent alone does not imply clipping, truncation, or an
+ellipsis; those are explicit paragraph policies.
+
+### Resolution environment
+
+The environment is a manifest, not an ambient promise. It identifies every
+external fact capable of changing layout:
+
+| Environment part | Required identity |
+| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
+| Font resources | Exact content identity, face index for collections, declared family metadata, and availability state. |
+| Font selection | Ordered candidates, family and fallback rules, synthesis policy, and permitted missing-glyph behavior. |
+| Language services | Unicode, bidirectional, segmentation, and line-breaking data versions, plus locale and hyphenation resources when relevant. |
+| Coordinate policy | Logical-unit scale and any text scale applied before layout. Device-pixel rounding is not part of text resolution. |
+| Safety limits | Declared bounds on input length, glyph count, line count, or work, when a host imposes them. |
+
+A family name, file name, URL, or operating-system font handle is not a
+sufficient font identity. The same name may resolve to different bytes. A
+portable result identifies the exact font content and face used, together with
+the effective variation coordinates, features, and any declared synthesis.
+
+There is no silent “system fallback.” A host may offer system fonts, but their
+resolved identities and order become part of the environment before a result
+is considered complete.
+
+### Text-oracle version
+
+The oracle version identifies the complete geometry-producing policy:
+shaping, bidirectional resolution, cluster construction, line breaking,
+alignment, truncation, font-metric interpretation, and numeric behavior.
+
+Any change that can alter a glyph choice, glyph position, line, baseline,
+mapping, or reported bound requires a new oracle version. Changing font bytes
+or a hyphenation dictionary changes the environment identity instead; changing
+how the oracle interprets those resources changes the oracle version.
+
+The resolved result records both identities. “Latest” is not a valid durable
+oracle version.
+
+## Resolution rules
+
+### Source preservation and preprocessing
+
+Resolution never rewrites authored text. Any transformed shaping text is a
+derived sequence in its own character coordinate space, with a complete,
+possibly many-to-many mapping back to source UTF-8 ranges.
+Controls that draw no glyph, explicit line terminators, and characters hidden
+by truncation still retain source mappings.
+
+Every source-backed layout unit is traceable to one or more complete source
+scalar ranges. Synthetic units such as an inserted hyphen or truncation marker
+have no source byte range and instead record the source boundary and policy
+that introduced them. A synthetic unit must never masquerade as authored
+content during copy, editing, or accessibility traversal.
+
+### Font selection and shaping
+
+Font fallback is resolved at the smallest source unit the oracle can shape
+correctly; it is not required to follow authored run boundaries. A paint-only
+run boundary may remain transparent to shaping only when every resulting
+cluster still has unambiguous paint ownership. Otherwise that boundary is a
+declared shaping boundary. This boundary policy is part of the oracle version
+and its cache identity; it cannot vary between measurement and painting.
+
+Each shaped run records the exact resolved face and shaping state. Glyph
+identifiers are meaningful only together with that face identity. Consumers
+must not reinterpret glyph identifiers against another version of a font.
+
+The oracle preserves fractional advances and offsets in logical units. Pixel
+snapping and raster-device hinting may affect final coverage, but they do not
+change the resolved line breaks, caret geometry, or base bounds.
+
+### Line construction
+
+Lines are ordered in block progression. Within a line, visual glyph order may
+differ from logical source order. The result therefore records both orders
+rather than assuming source byte offsets increase from left to right.
+
+Each line identifies:
+
+- the source ranges it covers, including any consumed line terminator;
+- whether its end is explicit, soft, terminal, or truncated;
+- its origin, baseline, ascent, descent, leading, advance, and line box;
+- the ordered shaped runs and clusters it displays; and
+- its logical and ink bounds.
+
+Every non-truncated paragraph has exactly one terminal line. An empty source
+therefore resolves to one empty terminal line carrying the default style's
+line metrics without inventing a source character. When the source ends in an
+explicit line terminator, the preceding line ends explicitly and the empty
+line after it is terminal. A terminal line is distinct from both a soft break
+and an explicit break.
+
+Trailing whitespace, zero-ink controls, and explicit terminators may affect
+logical coverage or caret placement without contributing ink. They remain
+represented.
+
+### Truncation
+
+Truncation occurs only when paragraph intent requests it. A line limit or box
+extent does not silently select a marker policy.
+
+When truncation is active, the marker is shaped in the same explicit
+environment and participates in fitting the final visible line. The result
+records:
+
+- that truncation occurred;
+- every omitted source range;
+- the source boundary to which the marker is anchored;
+- the marker's shaped glyphs, font identity, clusters, and bounds; and
+- caret and hit behavior at the truncation boundary.
+
+The marker is synthetic: copying the visible result does not insert it into
+source text unless a separate conversion explicitly asks for that behavior.
+The artifact must not assume omitted content is always one logical suffix;
+bidirectional presentation can make the visual omission more complex.
+
+### Coordinates
+
+All geometry is expressed in the text node's local logical coordinate space.
+The inline and block axes, line progression, and baseline direction are
+explicit. Fractional values are preserved. A parent transform maps the
+resolved local artifact into scene or device space; it does not cause text to
+be reshaped.
+
+## Resolved artifact
+
+The resolved text layout is immutable and self-identifying. At minimum it
+contains the following information.
+
+### Resolution identity
+
+- a fingerprint of every layout-affecting input;
+- the exact constraint input and final assigned text box;
+- the resolution-environment identity;
+- the text-oracle version; and
+- a record of fallback, synthesis, transformation, and replacement decisions
+ exercised during resolution.
+
+### Paragraphs and lines
+
+- paragraph boundaries and base-direction results;
+- lines in block order;
+- line origins, baselines, metrics, advances, and break kinds;
+- logical and ink bounds per line; and
+- alignment and vertical-placement results.
+
+### Shaped font and glyph runs
+
+- exact font-content and face identity;
+- effective size, axes, features, synthesis, script, language, and direction;
+- glyph identifiers in paint order;
+- per-glyph positions, advances, offsets, and base ink bounds; and
+- stable associations back to effective source style and paint selection.
+
+### Clusters and UTF-8 mapping
+
+- each cluster's shaping-text range, mapped source UTF-8 ranges, or explicit
+ synthetic status;
+- the glyph span, line, direction, advance, and logical bounds for that
+ cluster;
+- mappings from every source scalar boundary to its cluster and line;
+- mappings from every glyph and cluster back to source ranges;
+- legal caret stops, positions, visual order, and upstream/downstream
+ affinity; and
+- enough geometry to derive discontiguous selection regions in bidirectional
+ text.
+
+An offset inside a multi-byte UTF-8 scalar is invalid input to source-mapping
+queries; it is not rounded silently. A source range that produces no glyph
+still maps to a line and editing boundary where the text policy permits one.
+
+### Bounds
+
+The artifact keeps three extents separate:
+
+| Extent | Meaning |
+| ---------------------- | ------------------------------------------------------------------------------------------------------------------------ |
+| Assigned text box | The box obtained from authored size intent and constraints; surrounding box layout uses this extent. |
+| Logical content bounds | The union of typographic line boxes and advances, including layout space without glyph ink. |
+| Base ink bounds | The union of glyph, truncation-marker, inserted-hyphen, and layout-owned decoration ink before external paint expansion. |
+
+Node strokes, shadows, filters, and other paint effects expand visual bounds
+after text resolution. They must use the artifact's base geometry and must not
+feed altered measurements back into shaping. Color-glyph intrinsic drawing
+bounds are base ink; device antialiasing fringe is not.
+
+### Completeness invariants
+
+A complete result satisfies all of these conditions:
+
+1. Every visible glyph belongs to exactly one shaped run, line, and cluster.
+2. Every source scalar range has a defined source-to-layout disposition:
+ visible, zero-ink control, explicit break, transformed, or omitted by an
+ explicit policy.
+3. Non-synthetic clusters cover the represented shaping text without
+ accidental gaps or overlap. Their authored-source mapping may be
+ many-to-many only when an explicit transformation requires it; synthetic
+ clusters are labeled separately.
+4. Font identity and effective shaping state are complete for every glyph.
+5. Line, cluster, caret, logical-bound, and ink-bound geometry share one local
+ coordinate space.
+6. The environment and oracle recorded by the artifact are exactly those used
+ to produce it.
+7. No resource remains pending and no fallback decision remains implicit.
+
+## One result, many consumers
+
+The resolved artifact is the authority for every geometry-sensitive use:
+
+| Consumer | Required projection of the one result |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
+| Surrounding layout | Uses the assigned box and logical metrics; it does not estimate character widths. |
+| Painting | Uses the recorded faces, glyphs, positions, lines, and layout-owned marks; it does not reshape or rebreak. |
+| Hit testing | Uses line, cluster, and caret geometry, including visual order and affinity. |
+| Editing and selection | Uses UTF-8 mappings and legal caret stops; it does not infer positions from glyph count or code-point count. |
+| Bounds and damage | Starts from base ink bounds and applies known paint expansion; it does not substitute the assigned box for ink. |
+| Faithful export | Preserves recorded line breaks and glyph placement, embedding the resolved fonts or outlining when the target cannot express them exactly. |
+
+Measurement is therefore a query over the resolved artifact, not a parallel
+text operation. Likewise, painting is a realization of already resolved
+glyph geometry, not a second chance to select fonts or line breaks.
+
+An export whose purpose is semantic reflow may intentionally resolve the
+source again under a target environment. That is a conversion with a new
+resolution identity, not faithful consumption of the existing result.
+
+## Determinism, versioning, and caching
+
+For the same canonical layout-affecting inputs, environment identity, safety
+limits, and oracle version, resolution produces the same semantic artifact.
+This includes font choices, glyphs, line breaks, positions, mappings, and
+bounds under the oracle's declared numeric policy.
+
+Determinism does not extend across undeclared system fonts, mutable font URLs,
+different font bytes with the same family name, different Unicode or
+hyphenation data, or different oracle versions. Those are different inputs.
+
+Every cache key must account for every fact capable of changing layout,
+including:
+
+- source bytes and all layout-affecting attributes;
+- paragraph intent and constraints;
+- exact font-content identities and fallback order;
+- language, segmentation, breaking, and hyphenation data identities;
+- text scale and numeric policy; and
+- oracle version and declared safety limits.
+
+Paint-only changes need not invalidate the geometry artifact. A cache that
+also stores painted output must key that output on the paint state separately.
+The arrival or replacement of a font resource creates a new environment
+identity and a new resolved artifact; a supposedly immutable result is never
+patched in place by a late font swap.
+
+Incremental resolution and internal cache reuse are permitted only when the
+published artifact is equivalent to a complete fresh resolution under the
+same identity. Partial internal state is not a second public layout contract.
+
+## Validation and failure behavior
+
+The resolver returns either one complete artifact or a typed failure. It does
+not return plausible metrics with unresolved glyphs and let painting silently
+choose a different answer later.
+
+### Input validation failures
+
+These include:
+
+- malformed UTF-8 or a range boundary inside a UTF-8 scalar;
+- style ranges that overlap, leave gaps, reverse, or exceed the source;
+- incomplete layout-affecting style or paragraph state;
+- non-finite or contradictory constraints;
+- invalid font sizes, spacing, line limits, feature tags, or variation values;
+ and
+- a requested policy not supported by the selected oracle version.
+
+### Resolution failures
+
+These include:
+
+- a required font resource that is missing, pending, or has a different
+ content identity;
+- no permitted face for a source cluster;
+- no permitted glyph fallback for a cluster;
+- missing language, segmentation, or hyphenation resources required by the
+ declared policy;
+- an unsupported script, writing mode, or shaping feature; and
+- an exceeded declared safety limit.
+
+Diagnostics identify the source byte range and relevant style or paragraph
+property, state the failed policy, and name the resource identities or
+candidates involved. A missing-font diagnostic reports the requested family
+and resolved resource identity rather than presenting the failure as malformed
+source text.
+
+The default portable policy is strict: absent fonts and unresolved glyph
+coverage fail resolution. An environment may explicitly permit a particular
+missing-glyph face or replacement policy. If so, that policy and the exact
+replacement face become inputs, and the resulting replacement clusters remain
+fully represented. Silent tofu, silent font substitution, and silent feature
+dropping are never conforming behavior.
+
+An interactive host may display an explicitly diagnostic placeholder while a
+resource loads. That placeholder is not a resolved text-layout artifact and
+must not be cached, measured, exported, or presented as the final result.
+
+## Defaults
+
+Defaults at this boundary are deliberately independent of ambient platform
+state:
+
+| Input | Default |
+| ----------------------- | ----------------------------------------------------------------------------------------------- |
+| Unicode normalization | None; preserve the authored sequence. |
+| Source language | Undetermined (`und`) unless declared; no host locale is inferred. |
+| Base direction | Automatic from content, with left-to-right for a paragraph containing no strong character. |
+| Writing mode | Horizontal lines progressing top to bottom. |
+| Inline alignment | Start. |
+| Soft wrapping | Enabled at legal opportunities when the inline axis has a finite limit; otherwise no soft wrap. |
+| Line height | Normal font-metric line height under the selected oracle version. |
+| Maximum lines | Unbounded. |
+| Truncation | None. |
+| Missing fonts or glyphs | Strict resolution failure. |
+| Device-pixel rounding | None in the resolved artifact. |
+
+There is no universal default font face, font size, or fallback list in this
+RFD. The authoring language or enclosing document model supplies a complete
+default style, and the resolution environment supplies exact font resources.
+For example, Grida XML owns its own authored typography defaults; this RFD
+owns what happens after those defaults become complete inputs.
+
+## Conformance
+
+### Resolver
+
+A conforming resolver:
+
+1. **MUST** validate attributed source, paragraph intent, constraints,
+ environment completeness, and oracle support before claiming a result.
+2. **MUST** resolve font choice, shaping, clusters, bidirectional order, line
+ breaks, baselines, alignment, and requested truncation exactly once into a
+ complete immutable artifact.
+3. **MUST** retain exact font identities, source mappings, logical and visual
+ order, and the three distinct bound classes.
+4. **MUST** preserve fractional logical geometry and keep device rounding out
+ of the result.
+5. **MUST** expose the exact environment identity and oracle version used.
+6. **MUST NOT** silently substitute resources, drop shaping requests, repair
+ invalid ranges, or publish a partial artifact as complete.
+
+### Consumer
+
+A conforming geometry-sensitive consumer:
+
+1. **MUST** use the resolved artifact for measurements, glyph placement,
+ lines, hit testing, and source mapping relevant to its task.
+2. **MUST NOT** independently shape, wrap, select fallback fonts, or infer
+ caret positions for the same resolution.
+3. **MUST** request a new resolution when layout-affecting source,
+ constraints, environment, or oracle version changes.
+4. **MUST** treat semantic reflow under another target as a new resolution,
+ not as faithful rendering of the old artifact.
+
+## Relationship to authored formats
+
+This RFD owns shaped text resolution. An authored format owns its source
+syntax, defaulting rules, and mapping into the inputs above.
+
+[Grida XML](../format/grida-xml) stores Unicode content, flat attributed
+`tspan` runs, and text-box intent. Its format defaults and supported or future
+paragraph attributes project into paragraph intent. It does not serialize
+glyph identifiers, fallback choices, line breaks chosen by the oracle,
+baselines, caret positions, or measured bounds. Those are derived output.
+
+The relationship is one-way:
+
+```text
+authored Grida XML text
+ -> attributed source + paragraph intent + constraints
+ -> universal shaped text resolution
+ -> immutable resolved text layout
+```
+
+Writing a resolved artifact back into `.grida.xml` as if it were source intent
+would destroy responsiveness and bind the document to one font environment.
+A separate inspection or cache representation may persist the artifact only
+if it preserves its resolution identity and never claims to be canonical
+authored source.
+
+## Non-goals
+
+- Defining Grida XML's `text` or `tspan` syntax. The XML RFD owns that grammar.
+- Changing the scene paint model or introducing a text-specific paint model.
+- Defining font discovery, distribution, networking, licensing, or packaging.
+ The resolution environment identifies fonts after those concerns resolve.
+- Requiring identical raster coverage across graphics backends.
+- Persisting glyph positions as canonical authored document state.
+- Defining arbitrary scene nodes or widgets embedded inside text. A future
+ inline-object model must extend the input and artifact explicitly.
+- Defining editor commands, selection policy, or accessibility semantics. The
+ artifact supplies source and geometry mappings those systems may consume.
+- Claiming every oracle version supports every script, writing mode, text-on-a
+ path behavior, or typographic feature. Unsupported input fails explicitly.
+
+## Considered alternatives
+
+1. **One versioned resolved artifact — accepted.** It makes the actual shaping
+ result inspectable and gives every consumer the same lines, glyph geometry,
+ font choices, mappings, and bounds.
+2. **Fast approximate measurement followed by real shaping during paint —
+ rejected.** The approximation can choose a different box and different
+ lines, so every later correction occurs after surrounding layout has
+ already consumed the wrong geometry.
+3. **Let each consumer call the same shaping service independently —
+ rejected.** Equal algorithms are insufficient when constraints, font
+ availability, resource timing, defaults, or versions differ. It also turns
+ caret and export drift into cache-coordination problems.
+4. **Make the renderer's native paragraph object the contract — rejected.**
+ An opaque backend result does not guarantee inspectable UTF-8 mappings,
+ stable font identity, complete logical and ink bounds, or portability to a
+ second renderer and exporter.
+5. **Persist resolved glyphs and lines in authored source — rejected.** It
+ would freeze one environment's output into responsive intent, duplicate the
+ source string's authority, and create conflicting canonical
+ representations.
+6. **Use ambient operating-system text layout as the environment — rejected.**
+ Font aliases, fallback sets, language data, and versions vary across hosts.
+ Ambient behavior can participate only after it is resolved into an explicit
+ environment identity.
diff --git a/docs/wg/format/grida-xml-addressing.md b/docs/wg/format/grida-xml-addressing.md
new file mode 100644
index 0000000000..5092e84c5b
--- /dev/null
+++ b/docs/wg/format/grida-xml-addressing.md
@@ -0,0 +1,656 @@
+---
+title: "Grida XML durable addressing"
+description: "RFD for durable authored node and component-occurrence addresses, typed property targets, and immutable effective values before animation semantics are introduced."
+keywords:
+ - grida xml
+ - durable identity
+ - component instances
+ - property addressing
+ - effective values
+ - scene graph
+tags:
+ - internal
+ - wg
+ - format-schema
+ - canvas
+ - authoring
+ - collaboration
+format: md
+---
+
+# Grida XML durable addressing
+
+**Status:** Accepted direction for a Version 4 proving implementation.
+
+This RFD defines the identity and typed-value boundary that must exist before
+declarative animation can be specified. It does not define animation syntax,
+time, timelines, keyframes, interpolation, easing, composition, playback, or
+scheduling.
+
+The decision is:
+
+1. authored identity, component occurrence, property identity, compiled
+ runtime identity, and effective frame values are five different facts;
+2. Grida XML Version 4 gives every authored render node and every `use` a
+ durable owner-local `id`;
+3. a materialized node address combines its authored owner and member with an
+ ordered path of durable use occurrences;
+4. a property address adds one key from a closed typed property registry;
+5. a runtime may compile that durable address to a generation-checked handle;
+6. an immutable sparse value map may override registered base values for one
+ evaluation without mutating authored content; and
+7. an empty map is exactly the existing static scene.
+
+These decisions also serve inspection, durable diagnostics, previews, future
+cross-session operations under a stable canonical source-identity contract,
+and other evaluated-state systems. Animation is one future producer of
+effective values, not the owner of identity or the property registry.
+
+## Why one `id` is not the whole answer
+
+A component member can be authored once and materialized many times. The same
+member `label` under uses `first-card` and `second-card` must therefore retain
+one definition-local identity while producing two distinct occurrences. The
+materialized engine slot for either occurrence can also be deleted and reused.
+Finally, `opacity` and `width` on that occurrence have different types and
+invalidate different stages.
+
+Collapsing those facts into one string or integer makes at least one lifetime
+dishonest. The format instead keeps the following tiers separate:
+
+| Tier | Lifetime | Example | May be serialized as authored intent? |
+| ----------------------- | --------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------- |
+| Authored owner/member | Across parse, print, and unrelated edits | component `card`, member `label` | Yes |
+| Use occurrence path | Across parse, print, and unrelated edits | `[Use(SceneOwner(S), first-card)]` | Yes, as the individual `id` values on each `use` |
+| Property key | Across implementations of the same language version | `opacity` | Yes, when a later source construct needs a property target |
+| Compiled runtime target | One materialization generation | scene A, slot 42, generation 3, `opacity` | No |
+| Effective value | One declared evaluation | `opacity = 0.6` | No |
+
+Source spans, sibling indexes, element paths, human-readable `name` values,
+arena slots, hashes of property strings, and generated draw commands belong to
+none of the durable tiers.
+
+## Prior-art constraints
+
+The separation is a synthesis of browser and game-engine practice rather than
+a new all-purpose identifier:
+
+- Blink keeps a live target element and a typed property/attribute identity,
+ then writes sampled SVG values into a separate animated slot before style,
+ layout, and paint. The [Chromium SVG animation
+ study](../research/chromium/svg/animation-and-smil) records that pipeline.
+- Unity's
+ [`GenericBindingUtility`](https://github.com/Unity-Technologies/UnityCsReference/blob/979bc204a0c6506d87595a02fc89452687ed820d/Modules/Animation/ScriptBindings/GenericBinding.bindings.cs)
+ compiles hierarchy/type/property descriptors into
+ [`BoundProperty`](https://github.com/Unity-Technologies/UnityCsReference/blob/979bc204a0c6506d87595a02fc89452687ed820d/Modules/Animation/ScriptBindings/BoundProperty.bindings.cs)
+ handles whose runtime identity is an index plus version. Transient float,
+ integer, and object-reference values are supplied separately to `SetValues`.
+ The versioned index accelerates a binding; it does not make the authored
+ hierarchy path durable.
+- Unreal's
+ [`FMovieSceneObjectBindingID`](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/MovieScene/FMovieSceneObjectBindingID)
+ combines persistent binding identity with sequence-instance context, while
+ [`FMovieScenePropertyBinding`](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/MovieScene/FMovieScenePropertyBinding)
+ keeps property identity separate.
+
+The useful common shape is durable authored target, instance context, typed
+property, replaceable runtime handle, and transient value storage. None of
+these systems justifies serializing a runtime slot or using a hierarchy string
+as the only identity.
+
+## Scope
+
+This RFD specifies:
+
+- the Version 4 `id` grammar and uniqueness scopes;
+- component-root, member, use-occurrence, nested-use, and identity preservation
+ through slot projection;
+- the abstract node and property address forms;
+- the contract for a closed typed property registry;
+- nullability and sparse effective-value semantics;
+- compilation to replaceable generation-checked runtime targets;
+- strict validation and canonical-writing requirements; and
+- the static-equivalence law.
+
+It does not specify:
+
+- a textual selector or URI syntax for addresses;
+- paint-layer, stroke-layer, gradient-stop, text-run, or lens-operation member
+ identity;
+- mutation, collaboration, or merge protocols;
+- archive encoding;
+- animation or event semantics; or
+- incremental evaluation and compositor optimization.
+
+The abstract address is intentionally decided before a compact spelling. A
+later syntax can serialize the structure without forcing every processor to
+parse an opaque path string.
+
+## Version 4 source contract
+
+Version 4 inherits the complete Version 3 language: source modules, components,
+typed scalar parameters, and named slot projection. Its only new authored
+vocabulary is durable `id` on render elements and `use`.
+
+```xml
+
+
+
+ Ada River
+
+
+
+
+
+
+
+
+```
+
+### Required IDs
+
+In Version 4:
+
+- every authored render element has exactly one `id`;
+- every `use` has exactly one `id`;
+- `id` is a literal and cannot be supplied by a component parameter;
+- `id` uses the existing lowercase-kebab identifier grammar;
+- `name` remains an optional human label and is never an identity fallback;
+- `tspan`, `prop`, `arg`, `slot`, paints, strokes, and gradient stops do not
+ gain `id` from this RFD; and
+- the generated host viewport node has no authored identity.
+
+A `component` is a definition construct rather than an authored render
+element. Its existing `id` remains the module-local export symbol that
+qualifies the component owner; it is not copied or reused as a render-node ID.
+The root member is the tagged member variant `Root`; descendants use the
+tagged variant `Node(authored-id)`. The tag keeps `Root` distinct from a legal
+descendant `id="root"`.
+
+Versions 0 through 3 retain their exact grammar. They neither require nor
+accept render-node or use-occurrence `id`, and processors must not synthesize
+durable addresses for them.
+
+### Uniqueness scope
+
+One lexical owner owns one identity namespace:
+
+- the scene tree in one source unit is one owner; and
+- each component definition is a separate owner.
+
+Within an owner, render-element IDs and use IDs share one namespace and must be
+unique across the complete lexical subtree, including caller-authored slot
+assignments. Component export IDs remain in the source unit's component-export
+namespace and may coincide with a member ID without ambiguity.
+
+Sharing the render/use namespace guarantees unambiguous owner-local lookup and
+prevents two differently typed authored sites from claiming the same token.
+Refactoring `Node(card)` into `Use(card)` still changes identity and must be an
+explicit edit. Allowing IDs to repeat in different components keeps
+definition-local names concise and is safe because the owner is part of every
+address.
+
+Version 4 source units link only Version 4 component sources. A processor must
+not claim a complete Version 4 address space while expanding an older
+definition whose members and uses have no durable IDs. Older callers likewise
+cannot link a newer Version 4 definition.
+
+This deliberately tightens the earlier monotone link pattern:
+
+| Caller | Version 1 target | Version 2 target | Version 3 target | Version 4 target |
+| --------- | ---------------------------- | ------------------ | -------------------------- | --------------------- |
+| Version 1 | Static use | Invalid | Invalid | Invalid |
+| Version 2 | Static use / empty interface | Scalar-capable use | Invalid | Invalid |
+| Version 3 | Static use | Scalar arguments | Scalar arguments and slots | Invalid |
+| Version 4 | Invalid | Invalid | Invalid | Identity-complete use |
+
+Versions 1 through 3 remain unchanged. Version 4 trades backward linking for
+the stronger invariant that every authored node in its materialized closure
+has a durable address; partial identity would make target validity depend on
+which dependency happened to be older.
+
+### Stability and editing
+
+An ID survives parsing, canonical writing, reformatting, sibling insertion,
+reordering, reparenting within the same owner, and unrelated property edits.
+Changing an ID changes identity. A tool may rename and retarget references in
+one transaction, but a reader never guesses that two different IDs denote the
+same object.
+
+That is authored-member stability, not unconditional occurrence-address
+stability. Reparenting through a different component use or slot projection
+changes the ordered occurrence path. Changing canonical source identity,
+renaming a component export, changing a member/use ID, or changing an enclosing
+use path likewise changes the corresponding address.
+
+Moving a subtree within one owner preserves its IDs. Copying a subtree into an
+owner where any copied ID already exists must mint non-conflicting IDs before
+the copy becomes valid. The source language does not prescribe an editor's ID
+generation algorithm; it only requires the committed result to satisfy the
+grammar and uniqueness rules.
+
+## Address model
+
+### Authored owner
+
+An authored owner is one of:
+
+```text
+scene-owner = (canonical source identity, scene)
+component-owner = (canonical source identity, component export id)
+```
+
+Canonical source identity is supplied by the source-resolution environment,
+as already required by the module RFD. A relative `href`, source bytes, or
+working-directory path is not substituted for it.
+
+### Authored member
+
+An authored member is a tagged variant:
+
+```text
+Root
+Node(authored render-element id)
+```
+
+The pair `(owner, member)` identifies authored definition intent but not one
+materialized component instance.
+
+### Use occurrence
+
+A use occurrence is:
+
+```text
+(caller owner, authored use id)
+```
+
+The referenced component, source span, `href`, and `name` remain valuable
+provenance, but none replaces the caller-owned use identity.
+
+### Materialized node address
+
+A materialized node address is:
+
+```text
+(authored owner, authored member, ordered use-occurrence path)
+```
+
+The path is outermost first. An ordinary scene node has an empty path. A member
+inside one component use has one segment. A member inside nested uses has one
+segment per expansion boundary.
+
+For the example above, let `S` be the entry's canonical source identity. The
+two component roots and avatars differ only by their typed occurrence path:
+
+```text
+(ComponentOwner(S, profile-card), Root, [Use(SceneOwner(S), first-card)])
+(ComponentOwner(S, profile-card), Node(avatar), [Use(SceneOwner(S), first-card)])
+(ComponentOwner(S, profile-card), Root, [Use(SceneOwner(S), second-card)])
+(ComponentOwner(S, profile-card), Node(avatar), [Use(SceneOwner(S), second-card)])
+```
+
+The scene root's `id="feed"` is its member ID; it is not a use-path segment.
+
+Caller-authored slot content retains its caller owner and member. Its path
+still includes the receiving use and every enclosing use through which that
+caller content was materialized. Projection changes placement and painter
+context; it does not transfer authorship to the callee.
+
+One logical materialized node occurrence has one canonical node address.
+Distinct canonical addresses must never resolve to the same logical
+occurrence except where a future RFD explicitly defines an aliasing construct;
+Version 4 defines none. An implementation may still share immutable backing
+data for repeated component definitions, as long as each occurrence retains a
+distinct address and observable instance behavior.
+
+## Typed property addresses
+
+A property address is exactly:
+
+```text
+(materialized node address, registered property key)
+```
+
+Property keys come from one closed, versioned registry rather than arbitrary
+field paths. Each registry entry defines:
+
+- its canonical semantic key;
+- its exact value type, including any property-specific optional state;
+- the node kinds on which it applies;
+- how to read the authored base value;
+- validation of an effective value;
+- a conservative set of affected stages: measure, layout, transform, bounds,
+ paint, and resource; and
+- deterministic equality.
+
+The initial registry is exactly the following. Keys are semantic property
+identities, not necessarily XML attribute spellings. `active`, for example,
+is the positive runtime value projected from the inverse `hidden` source
+attribute; `layout`, `fills`, and `strokes` are whole model values assembled
+from their compact or structured source syntax before this boundary.
+
+Impact legend: M = measure, L = layout, T = transform, B = bounds, P = paint,
+and R = resource.
+
+| Semantic key | Exact value type | Applicability | Authored base projection | Impact |
+| -------------------------- | --------------------- | ------------------------------------------------------------ | ----------------------------------------------------------- | ----------- |
+| `x`, `y` | `AxisBinding` | Every node | Materialized axis binding | M/L/T/B/P |
+| `width` | `SizeIntent` | Frames, rectangles, ellipses, lines, paths, and text | Materialized fixed/auto width intent | M/L/T/B/P |
+| `height` | `SizeIntent` | Frames, rectangles, ellipses, paths, and text; never lines | Materialized fixed/auto height intent | M/L/T/B/P |
+| `min-width`, `max-width` | `OptionalNumber` | Frames, rectangles, ellipses, lines, paths, and text | Materialized optional width constraint | M/L/T/B/P |
+| `min-height`, `max-height` | `OptionalNumber` | Frames, rectangles, ellipses, paths, and text; never lines | Materialized optional height constraint | M/L/T/B/P |
+| `aspect-ratio` | `OptionalAspectRatio` | Rectangles, ellipses, and paths | Materialized optional width/height ratio | M/L/T/B/P |
+| `active` | `Boolean` | Every node | Positive activity value; inverse of source `hidden` | M/L/T/B/P/R |
+| `rotation` | `Number` | Every node | Clockwise degrees | M/L/T/B/P |
+| `flip-x`, `flip-y` | `Boolean` | Every node | Native mirror flags | M/L/T/B/P |
+| `flow` | `Flow` | Every node | In-flow or absolute participation | M/L/T/B/P |
+| `grow` | `Number` | Every node | Main-axis growth factor | M/L/T/B/P |
+| `self-align` | `SelfAlign` | Every node | Per-child alignment projected from source `align` | M/L/T/B/P |
+| `opacity` | `Number` | Every node | Node/subtree compositing opacity | P |
+| `layout` | `Layout` | Frames | Complete normalized layout behavior | M/L/T/B/P |
+| `clips-content` | `Boolean` | Frames | Content-clip flag projected from source `clips` | B/P |
+| `corner-radius` | `CornerRadius` | Frames and rectangles | Complete four-corner elliptical radius value | B/P |
+| `corner-smoothing` | `Number` | Frames and rectangles | Smoothing factor | B/P |
+| `fills` | `Paints` | Fill-paintable frames, rectangles, ellipses, paths, and text | Complete ordered fill paints after shorthand normalization | P/R |
+| `strokes` | `Strokes` | Stroke-paintable frames, shapes, and text | Complete ordered stroke geometries and their ordered paints | B/P/R |
+
+`Number` is not one universal numeric domain. Rotation is any finite number;
+growth is finite and non-negative; opacity and corner smoothing are finite in
+`[0, 1]`. Fixed sizes and present optional constraints are finite and
+non-negative. A present aspect ratio contains two finite positive numbers.
+Layout, corner, paint, gradient-stop, image-resource, and stroke values retain
+the validation rules of their static model and renderer contracts. Validation
+checks the complete effective node state, not only each replacement in
+isolation: for example, smoothing plus elliptical corner radii and per-side
+strokes plus smoothing must be rejected regardless of which member came from
+authored state. An aggregate paint or stroke that the renderer cannot
+materialize is invalid rather than silently omitted. Applicability and domain
+validation happen before evaluation.
+
+That shared authored-state fence includes finite, mathematically invertible
+gradient transforms and, for a linear gradient, a binary32 unit-space endpoint
+distance greater than `2^-15`. Authored source, canonical writing, and a
+replacement `Paints` value therefore cannot disagree about malformed gradient
+intent or a ramp that would be replaced with a degenerate color.
+
+Backend representability is a later, resolved-value question. A conforming
+evaluator composes each visible gradient transform with its actual resolved
+paint box using the backend's numeric representation, verifies that the
+resulting local matrix is invertible, and proves that its selected gradient
+implementation can construct the shader before publishing or drawing the frame.
+A very small or large authored transform may fail for one box and succeed for
+another; the paint box can worsen or rescue its conditioning. Failure must
+identify the owning node and whether the paint came from a fill, stroke, or
+text run, and must occur before any frame pixels are emitted. This evaluation-
+time failure does not make the mathematically valid authored aggregate invalid.
+
+The registry does not, by itself, make a property animatable. A later
+animation specification must select a subset and define interpolation,
+composition, boundary, and resource rules for every selected key.
+
+Source spelling and semantic property identity are related but not required to
+be byte-equal. For example, compact `fill="#fff"` and an expanded `fill`
+element both project the same ordered fills property. The source-level
+`hidden` spelling projects a visibility value rather than creating a second
+runtime concept. A processor must not create separate effective-value keys for
+compact and expanded syntax.
+
+Structural facts are outside this registry: node kind, parent, child order,
+component reference, use path, slot assignment, text-run partition, and path
+command topology cannot be changed by an effective-value entry.
+
+The first registry is deliberately node-level. Whole ordered fills and strokes
+are aggregate typed values because the current scene model owns those values at
+the node. Replacing either aggregate may change its internal list length;
+addressing one paint, stroke, stop, run, or lens operation remains invalid
+until that subobject has a durable identity contract.
+
+## Nullability and absence
+
+There is no universal overlay `null`. Two states must not collapse:
+
+1. **No effective-value entry** — read the authored base value.
+2. **One exact registered value** — use that validated typed value for this
+ evaluation.
+
+When the model property itself is optional, optionality belongs inside its
+registered type—for example, `OptionalNumber(None)` versus
+`OptionalNumber(Some(24))`. That typed `None` is a real property value and is
+still distinct from an absent map entry. It is never encoded as an empty
+string, zero, false, NaN, a generic null token, or a magic enum member.
+
+Source omission remains governed by the static property grammar. It may mean a
+default, inheritance, automatic sizing, or an absent optional value depending
+on that property. It is not automatically equivalent to effective-map
+absence.
+
+## Materialization and compilation
+
+Materialization produces an ordinary scene plus a checked bidirectional
+relationship between canonical node addresses and live scene nodes. It must
+reject an address collision rather than select one node by traversal order.
+
+A runtime may compile a property address to the equivalent of:
+
+```text
+(runtime scene incarnation, arena slot, slot generation, property key)
+```
+
+That compiled target is a replaceable acceleration artifact. It is valid only
+for the materialization generation against which it was checked. Deletion,
+slot reuse, rematerialization, identity edits, or relevant component-link
+changes require validation or recompilation. A stale target must fail closed;
+it must never affect a new node that happens to reuse the same slot.
+
+Slot generation alone is insufficient: two freshly materialized scenes can
+both contain slot 7 at generation zero. An implementation must therefore
+include a non-authored runtime-scene/incarnation scope or make applying a
+compiled target to any scene other than the exact borrowed scene
+unrepresentable. That scope remains a runtime lifetime guard and is not a
+sixth authored identity tier.
+
+Cloning or replacing a scene likewise requires rebinding unless the runtime
+can prove both values share one non-divergent arena identity. Slot generations
+must never wrap and revive an ancient target; exhaustion fails explicitly or
+retires the slot permanently.
+
+Neither the compiled target nor a hash derived from it may be written as the
+authored address. Conversely, a renderer does not resolve source strings or
+walk component definitions for every frame; it consumes compiled targets.
+
+## Immutable effective values
+
+An effective-value input is an immutable sparse map:
+
+```text
+compiled property target -> typed property value
+```
+
+Before evaluation it validates that every target is live and generation
+correct, every key applies to the target node, and every value has the declared
+type and domain. Duplicate targets are errors unless a later producer defines
+composition before constructing the map.
+
+The reference evaluation is:
+
+```text
+materialized ordinary scene + effective values + declared environment
+ -> measure and layout
+ -> transforms and bounds
+ -> draw list
+ -> raster and spatial queries
+```
+
+Every downstream consumer observes the same effective values. Layout-affecting
+values are available before measurement and layout. Paint-affecting values are
+not sampled independently by the painter. Queries use the resolved result of
+the same evaluation; query-relevant traversal and clip state must be captured
+there rather than supplied later through another independently pairable base or
+effective-value view. Damage includes draw-list changes so paint-only value
+changes cannot produce empty damage.
+
+The declared resource environment is a separate immutable input. A logical
+image reference can retain the same identifier while its decoded bytes or
+readiness changes, so equality of resolved geometry and draw-list commands is
+complete only under the same resource-environment snapshot. The resolved
+result, ordered draw list, and environment identity form one immutable frame
+product; complete visual comparison accepts two such products rather than
+separately pairable parts. A snapshot change must invalidate retained raster
+state and produce conservative damage for resource-backed items, or for the
+complete scene when affected items cannot be identified. Evaluation still
+performs no resource I/O. Rastering a retained frame product against a different
+resource-environment incarnation or revision must fail before drawing; a raw
+unchecked draw-list replay cannot claim complete-frame semantics.
+
+Evaluation never mutates the authored scene, writes effective values into
+source, emits document operations, performs source or resource I/O, or reads a
+clock.
+
+### Static-equivalence law
+
+The empty map is the permanent static oracle:
+
+```text
+effective-values = empty
+ => authored base values at every registered property
+ => identical resolved geometry and content
+ => identical draw list
+ => identical queries
+ => identical pixels
+```
+
+This law is exact, not approximate. The existing static entry point may remain
+as a thin call using the empty map. It must not become a separate semantic
+pipeline.
+
+## Validation and diagnostics
+
+A strict Version 4 processor rejects at least:
+
+- a render element or `use` without `id`;
+- an empty, malformed, bound, or duplicate `id`;
+- a Version 4 source linking a component source of another version;
+- a materialized address collision or a materialized node without one address;
+- an unknown property key;
+- a property key inapplicable to the target kind;
+- a value of the wrong registered type;
+- a generic null token instead of the key's exact registered value type;
+- a non-finite or out-of-domain numeric value;
+- an aggregate paint, stroke, corner, or layout value outside the renderer's
+ declared capability;
+- a valid replacement that forms an invalid complete node state with the
+ remaining authored values;
+- a stale runtime scene or slot generation;
+- two effective entries for the same compiled target; and
+- an attempt to address an unregistered subobject or structural field.
+
+Diagnostics identify the authored source and owner, member ID, complete use
+path, property key when present, and the immediate violated rule. Useful forms
+include:
+
+- `Version 4 requires a lowercase-kebab id`;
+- `duplicate id "avatar" in component "profile-card"; first declared on `;
+- `member "label" through scene use "second-card" has no registered property "font-family"`;
+- `property "max-width" expects OptionalNumber, received Number`;
+- `property "fills" contains an image fit that this renderer cannot materialize`;
+- `corner-smoothing cannot be nonzero with elliptical corner radii`;
+- `compiled target for use "first-card", member "avatar" belongs to a replaced scene`; and
+- `compiled target for use "first-card", member "avatar" is stale: expected generation 2, found 3`.
+
+Source spans may enrich those messages. They do not become address fields.
+
+## Canonical writing
+
+A canonical Version 4 writer:
+
+- emits `version="4"`;
+- emits every required render and use ID unchanged at the semantic level;
+- never substitutes `name`, source position, or a generated path for a missing
+ ID;
+- preserves component export ID separately from member IDs;
+- writes the existing canonical static property forms;
+- never serializes occurrence paths, runtime handles, generations, effective
+ values, or resolved output as duplicate authored state; and
+- refuses to write an invalid identity graph rather than silently renaming it.
+
+An editor may generate or repair IDs as an explicit authored edit before
+calling the canonical writer. The writer itself is not an identity policy.
+
+## Current model compatibility
+
+The current scene contract distinguishes a stable string user-node ID from an
+ephemeral numeric runtime node ID, which is the correct lifetime split for an
+ordinary flat scene. Its lookup namespace is scene-global, however, and one
+string does not preserve Version 4's definition-local member plus ordered use
+path. Repeating one component member would either collide or require a
+flattened generated string whose segments and escaping become an accidental
+second address grammar.
+
+Version 4 therefore keeps its structured address in the linked/materialized
+program above the ordinary scene. A runtime that requires one flat user-node
+key may lower each logical occurrence to a collision-free opaque key, but it
+must preserve the structured source address for inspection, diagnostics,
+recompilation, and source writing. The opaque key is not the canonical authored
+address and must not be reconstructed by splitting a concatenated string.
+
+This is a declared incompatibility with representing live component
+occurrences through the current flat ID field alone. The RFD does not change
+the scene paint, ordered-paint, stroke, layout, or runtime-slot models, and it
+does not require the ordinary materialized scene to become component-aware.
+
+## Considered alternatives
+
+| Alternative | Decision | Reason |
+| ------------------------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------- |
+| `name` as identity | Rejected | Human labels are optional, duplicate, and routinely edited. |
+| Source span, sibling index, or element path | Rejected | Reformatting, insertion, reorder, and reparenting change them. |
+| Arena `NodeId` as authored identity | Rejected | Runtime storage identity has the wrong lifetime and may be reused. |
+| One globally flattened instance/member string | Rejected | It obscures owner and occurrence boundaries and weakens diagnostics and refactoring. |
+| Generated IDs for Versions 0–3 | Rejected | Structural generation cannot promise stability across ordinary source edits. |
+| Optional IDs in Version 4 | Rejected | A processor could not promise a complete durable address space or canonical writer. |
+| Reflective string property paths | Rejected | Type, applicability, nullability, invalidation, and diagnostics would become late and inconsistent. |
+| Clone and patch the authored scene per evaluation | Rejected | It creates a second mutation path and loses authored/effective separation. |
+| Identify paint list members by index now | Deferred | Reordering and insertion would retarget values; subobject identity needs its own RFD. |
+| Introduce animation syntax with identity | Rejected for this RFD | Timing and interpolation decisions are independent and remain open. |
+
+## Conformance
+
+A conforming Version 4 reader and materializer:
+
+1. **MUST** enforce required owner-local render and use IDs and the exact
+ version-link rule.
+2. **MUST** preserve owner/member identity and ordered occurrence paths across
+ component expansion and slot projection.
+3. **MUST** expose a one-to-one canonical address for every authored
+ materialized node and reject collisions.
+4. **MUST NOT** treat provenance, source spans, names, runtime slots, or
+ generations as authored identity.
+5. **MUST** validate typed property targets through one closed registry.
+6. **MUST** distinguish sparse absence from an optional state inside an exact
+ registered value type and **MUST NOT** define a universal overlay null.
+7. **MUST** reject stale compiled targets and invalid typed values before
+ evaluation.
+8. **MUST** keep authored state immutable and make an empty effective-value
+ input observationally identical to static evaluation.
+9. **MUST** feed one effective evaluation to layout, draw-list construction,
+ query, damage, and rendering.
+10. **MUST** treat resource-environment revision as frame identity for cache
+ and damage even when logical resource identifiers are unchanged.
+11. **MUST NOT** claim that registration makes a property animatable.
+
+## Deferred work
+
+Animation remains governed by the [Grida XML animation question set](./grida-xml-animation).
+That work still needs to decide time representation, source placement,
+target-reference spelling, keyframes, interpolation, easing, composition,
+events, static processing, resource behavior, and playback.
+
+Durable identity for paint layers, strokes, gradient stops, text runs, path or
+vector members, and lens-operation parameters is also deferred. Until each
+domain has stable member identity, a cross-target property address may name
+only the registered node-level value. A later RFD may add structured member
+segments without changing the five-tier separation accepted here.
diff --git a/docs/wg/format/grida-xml-animation.md b/docs/wg/format/grida-xml-animation.md
new file mode 100644
index 0000000000..2676ae4207
--- /dev/null
+++ b/docs/wg/format/grida-xml-animation.md
@@ -0,0 +1,443 @@
+---
+title: "Grida XML animation — day-one questions"
+description: "Open RFD asking whether and how .grida.xml should carry declarative animation from its first stable version, using SVG and Web Animations as problem-shaping references without adopting a syntax or runtime model."
+keywords:
+ - grida xml
+ - animation
+ - declarative animation
+ - timeline
+ - keyframes
+ - svg animation
+tags:
+ - internal
+ - wg
+ - format-schema
+ - canvas
+ - authoring
+ - rendering
+ - svg
+format: md
+---
+
+# Grida XML animation — day-one questions
+
+**Status:** Open RFD — question framing only. No syntax, timing model, or
+runtime behavior in this document is adopted.
+
+Two prerequisites are now decided outside this animation RFD. [Grida XML
+durable addressing](./grida-xml-addressing) defines Version 4 authored
+owner/member IDs, component occurrence paths, typed property targets, compiled
+runtime handles, and an immutable sparse effective-value boundary. The
+underlying property registry remains distinct from the future **animatable**
+subset. These decisions remove identity and document-mutation as blockers; they
+do not choose animation syntax or behavior.
+
+## The question
+
+Should declarative animation be part of the first stable Grida XML language,
+and, if so, what is the smallest coherent model that keeps animated documents
+inspectable, seekable, editable, and portable?
+
+The question belongs beside the [Grida XML RFD](./grida-xml), because it is
+about authored source rather than one renderer or editor. A general animation
+runtime may eventually deserve its own canvas-level specification. This RFD
+does not define that runtime; it asks what contract the authored language must
+leave room for from day one.
+
+“Support from day one” may mean any of the following, and the distinction is
+itself open:
+
+1. the first stable language fully parses and plays a minimal animation
+ subset;
+2. the first stable language defines the source model, even if some processors
+ expose only a static frame;
+3. the first stable language merely reserves the structural positions and
+ names needed for a later compatible addition.
+
+These are materially different commitments. This RFD exists so that the
+static scene language does not accidentally choose among them by omission.
+
+## Why ask before the static language hardens?
+
+Animation is not only “values changing over time.” It affects several
+foundational contracts:
+
+- how animation populates the accepted authored and effective-value tiers;
+- whether animation is nested with its target or stored in a separate
+ timeline graph;
+- how the accepted structured node target is spelled, owned, and copied;
+- whether layout observes animated geometry;
+- how multiple animations combine on one property;
+- what a static renderer, thumbnailer, exporter, or agent sees;
+- whether time- and event-dependent source remains deterministic and safe.
+
+The durable-addressing RFD has closed the property-tier and node-identity
+questions. Deferring the remaining distinctions could still require a second
+canonical source shape. Raising them now does not require implementing all of
+animation now.
+
+## What SVG contributes to the question
+
+SVG is a useful reference because declarative animation is part of its element
+tree rather than an unrelated movie format. SVG defines animation elements
+such as `animate`, `set`, `animateTransform`, and `animateMotion`; SVG 2 also
+recognizes that declarative animation may be enabled or disabled by a
+processing mode. The [SVG animation chapter](https://www.w3.org/TR/SVG2/animate.html)
+and [SVG 2 conformance processing modes](https://www.w3.org/TR/SVG2/conform.html)
+are the primary references.
+
+The strongest lessons are model-level, not spelling-level:
+
+- **Base and animated values are different facts.** Editing the authored value
+ must not overwrite it with one sampled frame. Visual output at time `t` may
+ legitimately differ from source.
+- **Timing and value interpolation are separate problems.** A timeline turns
+ time into progress; a value model turns progress into a property value. The
+ [Web Animations model](https://www.w3.org/TR/web-animations-1/) makes this
+ separation explicit.
+- **Discrete change deserves first-class treatment.** SVG's `set` does not
+ pretend that every string, boolean, enum, or resource reference can be
+ interpolated.
+- **Several effects may target the same property.** SVG/SMIL defines ordering,
+ replacement, addition, accumulation, and post-interval behavior rather than
+ relying on document order alone.
+- **Motion is more than x/y interpolation.** A motion path has path geometry,
+ progress along the path, and orientation behavior.
+- **Synchronization grows quickly.** Offset times, repeats, event starts, and
+ one animation beginning relative to another form a dependency graph, not a
+ collection of independent timers.
+
+The existing [Chromium SVG animation study](../research/chromium/svg/animation-and-smil.md)
+records how those distinctions materialize in a browser. The
+[SVG element model](../../reference/svg/element-model.md) records the
+round-trip hazard: edit decisions concern the base value while rendering may
+show an animated value.
+
+SVG also supplies warnings rather than templates. Its SMIL and CSS animation
+systems can target the same element and require precedence rules. Event timing
+and animation of reference-valued attributes enlarge the active-content and
+security surface; see the [untrusted SVG rendering study](../research/untrusted-svg-rendering.md).
+Grida XML should not inherit this complexity merely because the element names
+are familiar.
+
+The cross-project
+[motion graphics authoring landscape](../research/motion-graphics/index.md)
+surveys how Lottie, After Effects, Blender, Rive, dotLottie, Cavalry, Apple
+Motion, PowerPoint, and Keynote extend the property-animation kernel through
+clips, nested time, procedural value sources, controllers, templates, and
+authoring views. It is descriptive prior art, not part of this RFD.
+
+## Illustrative syntax probes, not proposals
+
+An SVG-like child element is one possible shape:
+
+```xml
+
+
+
+```
+
+A structured keyframe child is another:
+
+```xml
+
+
+
+
+
+
+```
+
+A separate timeline graph is a third:
+
+```xml
+
+
+
+
+
+
+```
+
+None of these fragments is valid Grida XML today. They expose different
+tradeoffs:
+
+| Shape | Attractive property | Immediate question |
+| ------------------------- | ------------------------------------------- | --------------------------------------------------------------------------- |
+| Animation child of target | Local, readable, SVG-like | How are animation property children distinguished from render children? |
+| Structured keyframes | One grammar for arbitrary ramps | Is it unnecessarily verbose for the common two-value case? |
+| Separate timeline graph | Central sequencing and cross-target editing | How does `target` spell the accepted structured node address? |
+| Named reusable animation | Reuse across nodes or instances | Are overrides and instance-local time worth a definition system on day one? |
+
+The RFD should not select a spelling until the value, timing, targeting, and
+composition questions below have answers. Syntax chosen first will conceal
+rather than resolve those model decisions.
+
+## Open question set
+
+### 1. Base value and sampled value
+
+- Does an active animation always produce an entry in the accepted effective
+ value map, even when that entry equals the authored base?
+- Is sampling a pure function of `(document, environment, time)`, or may prior
+ playback state affect the result?
+- When an animation ceases contributing, when is its effective entry removed,
+ and may fill behavior keep the last contribution?
+- When an editor changes a property during playback, is it editing the base,
+ the selected keyframe, an animation-relative offset, or a new override?
+- Can a canonical writer ever serialize a sampled frame, or only authored
+ animation intent?
+- What does inspection show: base, sampled, both, or an explicit mode?
+
+Grida has accepted the same safe separation in its static/effective boundary.
+Animation still needs to define contribution lifetime and public vocabulary.
+
+### 2. Timeline ownership
+
+- Is there exactly one document timeline, or can a subtree, component,
+ presentation page, or embedded asset own a timeline?
+- What establishes time zero?
+- Does a document autoplay, open paused at zero, or defer playback policy to
+ the host?
+- Is document duration explicit, derived from effects, infinite, or absent?
+- Are time values integer ticks, decimal seconds, or unit-bearing values such
+ as `600ms`?
+- What precision and boundary rules make sampling deterministic across
+ implementations?
+- Must arbitrary seeking produce the same result as continuous playback to
+ that time?
+
+### 3. Effect timing
+
+- Which day-one fields are indispensable: delay/begin, duration, iterations,
+ direction, easing, end delay, playback rate, and before/after fill behavior?
+- Are SVG-like `fill="freeze|remove"` terms desirable, or too easily confused
+ with visual fill paints?
+- Does an omitted duration mean invalid, instantaneous, inferred, or
+ indefinite?
+- Are negative delays and fractional iterations allowed?
+- What occurs exactly at begin, repeat, and end boundaries?
+- Is an always-seekable offset-only model sufficient initially, or is
+ interval scheduling part of the minimum coherent kernel?
+
+### 4. Keyframes and value sources
+
+- Is a two-value `from`/`to` form canonical, shorthand, or absent in favor of
+ keyframes?
+- Can a keyframe omit a value and use the underlying/base value?
+- Are offsets required, inferred, or mixed?
+- Is easing attached to the whole effect, to each segment, or both?
+- Is there a distinct discrete `set` operation for non-interpolable values?
+- Can values be relative (`by`, additive delta), or are all keyframes absolute?
+- How are animation values typed and validated against their target property?
+
+### 5. Animatable property registry
+
+- Is every authored property animatable unless refused, or only properties
+ explicitly registered as animatable?
+- Which initial categories are coherent: numbers, positions, sizes, colors,
+ opacity, transforms, paint properties, paths, text, and layout controls?
+- Are `auto`, intrinsic sizes, spans, and other intent values interpolable,
+ discrete, or invalid in animation?
+- Does animating layout intent re-run layout at each sample?
+- Can hierarchy, node type, child order, component reference, or resource
+ identity change over time?
+- Should properties that can cause I/O or change trust boundaries be
+ categorically non-animatable?
+
+The registry is a language contract. Leaving interpolation behavior to each
+renderer would make one source file produce different designs.
+
+### 6. Interpolation semantics
+
+- Which color space and alpha model interpolate colors and gradient stops?
+- How do angles choose a direction and handle multiple turns?
+- Are transforms interpolated as matrix entries, decomposed components, or
+ typed transform operations?
+- What happens when two transform lists have different operation shapes?
+- What correspondence rules make two paths morphable?
+- How do paint stacks or gradients with different layer/stop counts combine?
+- For non-interpolable pairs, does the language reject the animation or fall
+ back to a defined discrete step?
+
+### 7. Multiple effects on one property
+
+- Does the later animation replace the earlier one, or is priority based on
+ begin time, source order, explicit priority, or timeline order?
+- Are replace, add, and accumulate separate composition modes?
+- Which value is the “underlying” value for each effect in a stack?
+- Does repeated additive animation accumulate per iteration?
+- How does a frozen/held effect interact with a later active effect?
+- Can transitions and authored animations coexist, or should Grida have one
+ animation engine and one precedence model?
+
+SVG's animation sandwich and Web Animations' effect stack show that this is a
+bedrock question, even if day-one syntax permits only replacement.
+
+### 8. Targeting and identity
+
+- Is an animation always nested under and implicitly targets its parent?
+- Can it target another node, paint, gradient stop, text run, component
+ parameter, or effect?
+- Cross-targeted nodes now have the structured Version 4 address from the
+ [durable-addressing RFD](./grida-xml-addressing); what source spelling should
+ carry that address remains open.
+- Node properties now use typed registry keys rather than reflective runtime
+ field paths. Durable addressing for paint, stop, text-run, and other
+ subobjects remains deferred.
+- What happens when the target is deleted, renamed, moved, or instantiated
+ through a component?
+- Does copying a subtree copy, retarget, share, or detach its animations?
+
+Avoiding external targeting initially may still simplify source syntax, but it
+is no longer required to compensate for missing node identity. The source and
+copying tradeoffs remain open.
+
+### 9. Transform and motion-path specialization
+
+- Is transform animation ordinary property animation, or does it need a typed
+ `animate-transform` form to preserve operation semantics?
+- Is motion along a path representable as x/y/rotation keyframes, or does a
+ motion-path effect need its own path, distance, orientation, and anchor
+ model?
+- Can a motion path reference scene geometry, or must it own immutable path
+ data?
+- Does motion compose before or after the node's authored transform,
+ constraints, native rotation, and layout placement?
+- Can layout observe motion, or is it always visual-only?
+
+### 10. Synchronization and triggers
+
+- Is day-one timing limited to absolute offsets from one timeline?
+- May one animation begin relative to another animation's begin, repeat, or
+ end?
+- Are event-triggered begins part of the authored language?
+- If events exist, what event vocabulary exists in a non-DOM scene?
+- Are scroll-, audio-, presentation-cue-, or data-driven timelines future
+ timeline types or day-one requirements?
+- Can cyclic synchronization dependencies be represented, and if so how do
+ they resolve?
+- Does deterministic export refuse event-driven animation, supply an event
+ trace, or select a fallback frame?
+
+SVG demonstrates the expressive value of sync-base and event timing, while
+also demonstrating their implementation and security cost.
+
+### 11. Layout, rendering, and performance tiers
+
+- Is every animation sampled before measure/layout, between layout and paint,
+ or according to the property it targets?
+- Which animations require re-measurement, re-layout, repaint, or only
+ transform/opacity recomposition?
+- Is that classification normative so processors cannot disagree about
+ geometry?
+- Can a processor lower eligible effects to a compositor without changing the
+ sampled result?
+- What frame-rate-independent sampling contract applies when frames are
+ skipped?
+- How are dynamic visual bounds, damage, hit testing, clipping, and culling
+ computed over time?
+
+Performance optimization is not a source-language feature, but the language
+must not make the correct sampling order ambiguous.
+
+### 12. Resources, safety, and processing modes
+
+- May animation target image source, link destination, component reference,
+ font resource, or any other reference-valued property?
+- Can a sampled value initiate resource loading or network access?
+- Are event-triggered animations declarative-only, or can they invoke actions?
+- Does Grida XML define an inert/static processing mode that ignores animation
+ elements, a paused mode that samples at a supplied time, and a playback mode?
+- What is the required fallback frame when animation is unsupported or
+ disabled?
+- Should reduced-motion preference be an environment input, authored
+ alternative, or both?
+
+SVG shows that “no script” is not by itself a complete safety boundary when
+declarative animation can retarget references or react to events.
+
+### 13. Static consumers and export
+
+- What frame does a thumbnailer render when no time is supplied?
+- Does static export choose time zero, a poster time, the base scene, or reject
+ an animated document without an explicit choice?
+- How are finite animations exported to video, animated raster, slides, or a
+ frame sequence?
+- Is frame rate source intent, export policy, or merely a sampling choice?
+- Can an animated embedded document keep its own clock, and does a repeated
+ image fill restart or share that clock?
+
+### 14. Canonical source and authorability
+
+- Is there exactly one canonical representation of keyframes and timing?
+- Are animation property names the same names used by static source?
+- Where may animation property elements appear relative to fills and render
+ children?
+- Does source order carry semantic priority, or only human-readable order?
+- Which defaults can safely be omitted without making timing hard to inspect?
+- Can a human or language model predict the state at a named time without
+ executing hidden rules?
+- How are invalid targets, type mismatches, unsorted offsets, duplicate
+ offsets, cycles, and unsupported interpolation reported?
+
+### 15. Import, preservation, and interoperability
+
+- Does importing animated SVG preserve animation as authored intent, translate
+ only a supported subset, sample one frame, or refuse animated content?
+- Can unsupported timing or composition be preserved losslessly without being
+ executed?
+- Does exporting Grida animation to SVG target SVG animation elements, CSS
+ keyframes, a flattened frame sequence, or a capability-dependent choice?
+- How are semantic losses reported rather than silently baked?
+- Is “SVG-like” a source-syntax goal, a behavioral compatibility goal, or only
+ inspiration for the model split?
+
+### 16. Conformance and testing
+
+- What exact-time samples form a minimal conformance corpus?
+- Must sampling a document at time `t` be byte- or value-equivalent whether
+ reached by seeking, forward playback, reverse playback, or dropped frames?
+- Which boundary times test begin/end inclusion, repeats, easing endpoints,
+ discrete changes, and held values?
+- How are cross-implementation tolerances defined for interpolated colors,
+ transforms, paths, and raster output?
+- What test proves that playback never mutates authored base intent?
+
+## Candidate day-one scopes to compare
+
+These are comparison points, not recommendations.
+
+| Candidate | Stable on day one | Deferred | Main risk |
+| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
+| Reservation only | Structural slots/names and a declared static fallback | All executable animation | The reservation may encode the wrong future model |
+| Minimal local effects | Parent-targeted replace animation; absolute begin, duration, finite repeat; numeric/color/opacity/transform values; deterministic seeking | Events, sync-base, motion paths, additive composition, reusable timelines | “Minimal” may still choose irreversible timing and interpolation defaults |
+| SVG-shaped subset | `animate`, `set`, transform, and motion concepts with familiar timing fields | The long tail of SMIL timing | Familiar syntax may imply compatibility the behavior does not meet |
+| Timeline/keyframe core | One abstract timeline/effect/keyframe model shared by markup, editor, and runtime | Specialized sugar and event timelines | More abstract and verbose for simple authored scenes |
+| Full declarative system | Targeting, synchronization, composition, motion, and processing modes | Only scripting/API control | Excessive first-version complexity and a large active-content surface |
+
+The next RFD step is not to pick a tag name. It is to decide which row is the
+smallest scope that preserves the invariants Grida cannot afford to retrofit.
+
+## Decision gates before any normative draft
+
+A normative animation proposal should not begin until it can answer, with
+examples and counterexamples:
+
+1. How does animation populate the already-separated base and effective-value
+ tiers?
+2. What timeline and deterministic-seeking model is canonical?
+3. What is the day-one animatable property registry?
+4. How do effects on the same property compose?
+5. How does animation spell, own, and copy the accepted structured targets?
+6. Which animation constructs can affect layout, resources, or trust?
+7. What does every static consumer render?
+8. Which syntax is canonical, and why is it easier to author and diagnose than
+ the alternatives?
+9. What is preserved, translated, sampled, or refused when importing SVG?
+10. What conformance corpus would let a second implementation produce the same
+ sampled scene?
+
+Until those gates close, all animation-like XML fragments remain illustrative
+only and a strict Grida XML reader should continue to reject them rather than
+guess at semantics.
diff --git a/docs/wg/format/grida-xml-component-parameters.md b/docs/wg/format/grida-xml-component-parameters.md
new file mode 100644
index 0000000000..64daa2186e
--- /dev/null
+++ b/docs/wg/format/grida-xml-component-parameters.md
@@ -0,0 +1,1002 @@
+---
+title: "Grida XML component parameters"
+description: "Open RFD proposing statically evaluated typed scalar props and arguments for Grida XML components while keeping the rendering scene component-blind."
+keywords:
+ - grida xml
+ - component props
+ - component arguments
+ - typed parameters
+ - static materialization
+ - slots
+tags:
+ - internal
+ - wg
+ - format-schema
+ - canvas
+ - authoring
+ - architecture
+ - resources
+ - scene-graph
+ - text
+format: md
+---
+
+# Grida XML component parameters
+
+**Status:** Open RFD — selected Version 2 scalar-parameterization delta with a
+proving implementation of all six scalar types, defaults and required
+arguments, bindings, brace escaping, forwarding, resource origins,
+specialization provenance, ordinary-scene materialization, and local-file
+rendering. Canonical source writers and complete-library validation remain
+unimplemented. None of the parameter vocabulary introduced by this RFD is
+valid Version 0 or Version 1 syntax.
+
+**Companion specifications:** [Grida XML](./grida-xml) owns ordinary authored
+scene values. [Grida XML modules and static component
+reuse](./grida-xml-modules) owns source units, boxed component definitions,
+`use`, linking, materialization, and component provenance. This RFD specifies
+only the parameterization delta. [Grida XML component
+slots](./grida-xml-component-slots) separately owns the Version 3 named render
+projection delta. [Grida XML durable addressing](./grida-xml-addressing) owns
+the later Version 4 member/use identity delta.
+
+## Decision summary
+
+Grida XML components may declare a small, typed scalar interface. A use
+supplies explicit arguments to that interface. Parameter evaluation completes
+during linking and materialization; layout and rendering receive only an
+ordinary concrete Grida scene.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {title}
+
+
+```
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+The proposal makes these choices:
+
+| Question | Proposed answer |
+| -------------------------- | --------------------------------------------------------------------- |
+| Ownership | Grida XML source linker and materializer |
+| Rendering representation | Existing ordinary scene nodes only |
+| Declaration | Leading direct `` child of `component` |
+| Argument | Direct `` child of `use` |
+| Binding | Exact `{prop-name}` reference in an established scalar value position |
+| Value model | Closed typed scalar set; no generic value or XML fragment |
+| Missing argument | Declaration default, otherwise an error |
+| Null | Not a Version 2 value |
+| Forwarding | Exact outer-prop reference as a nested argument value |
+| Expressions and reactivity | Outside Grida XML parameterization |
+| Render-valued input | Proposed Version 3 named-slot delta; invalid in Version 2 |
+
+## Why Grida owns this above rendering
+
+A component parameter is authored intent. It states that several concrete
+values come from one declared component API and that omitted callers continue
+to track the definition's default. Flattened render nodes cannot recover that
+relationship.
+
+Leaving even static parameters entirely to a secondary runtime would make
+that runtime—not Grida XML—the source of truth for defaults, resource origin,
+diagnostics, linked editing, and canonical writing. Grida XML would become a
+generated render interchange rather than a complete file-first authored
+language.
+
+Parameterization is nevertheless not a paint, geometry, layout, or runtime
+node concept. Its boundary is:
+
+```text
+entry source + source environment
+ → parse and link source units
+ → resolve typed component arguments
+ → substitute scalar bindings
+ → validate and materialize ordinary scene + provenance + resources
+ → resolve layout, text, paths, and paints
+ → render
+```
+
+No prop declaration, argument, binding token, default state, or unresolved
+value enters ordinary scene resolution. A low-level consumer may continue to
+supply an already concrete scene without using components. A higher-level
+runtime may generate Grida XML or concrete scenes, but one is not required for
+static component customization.
+
+## Goals
+
+Version 2 scalar parameterization must:
+
+- let a component expose a deliberate, inspectable API rather than its entire
+ internal property tree;
+- keep call sites independently parseable through explicit `arg` elements;
+- type values before they enter ordinary property validation;
+- distinguish an omitted argument from an explicit value equal to the
+ current default;
+- support exact forwarding through nested components without introducing an
+ expression language;
+- retain the lexical origin of resource-valued defaults and arguments;
+- produce a finite, fully concrete ordinary scene before layout or rendering;
+- preserve declarations, arguments, bindings, and their provenance during
+ source writing; and
+- remain predictable for direct human and language-model authoring.
+
+## Non-goals
+
+This proposal does not define:
+
+- conditions, loops, arithmetic, functions, property paths, or a general
+ expression language;
+- state, events, async values, data fetching, or reactive updates;
+- dynamic component references or parameterized `href`;
+- implicit conversion through a generic string type;
+- nullable values or parameterized attribute presence;
+- whole paints, stroke lists, paths, transforms, collections, or render nodes
+ as prop values;
+- arbitrary component attributes directly on `use`;
+- deep descendant overrides or patches;
+- component variants or inheritance;
+- durable instance or component-descendant identity;
+- live-component persistence in the packed archive; or
+- render-valued slots, children, or fallback subtrees.
+
+The named direct-projection subset now has a focused Version 3 proposal in
+[Grida XML component slots](./grida-xml-component-slots). It does not alter
+this Version 2 boundary. Fallback subtrees and other absence policies remain
+deferred.
+
+## Vocabulary
+
+| Term | Meaning |
+| ----------------------- | ---------------------------------------------------------------------- |
+| **Prop** | One named typed scalar member declared by a component |
+| **Argument** | One explicit value supplied by a use for a declared prop |
+| **Default** | Definition-owned literal selected when a use omits that prop |
+| **Binding** | One authored scalar sink whose complete value comes from a prop |
+| **Prop environment** | The complete effective prop values for one component use |
+| **Forwarding** | Supplying an outer effective prop as an argument to a nested use |
+| **Value origin** | The source unit and source location where a literal value was authored |
+| **Specialization** | Resolving one use's prop environment and substituting its bindings |
+| **Render-valued input** | Caller-authored scene content supplied to a component |
+
+Component ID, prop name, use occurrence, argument, binding target,
+materialized node handle, and slot identity are different facts. This RFD does
+not substitute one for another.
+
+## Version relationship
+
+Version 2 inherits the complete Version 1 module and boxed-component model,
+then adds only the syntax and semantics in this RFD.
+
+| Source relationship | Result |
+| ------------------------------------------- | ------------------------------------------------- |
+| Version 0 source | Has no component vocabulary |
+| Version 1 component used by Version 1 | Static use; `use` remains empty |
+| Version 2 component used by Version 2 | Scalar props and arguments are available |
+| Version 1 component used by Version 2 | Accepted as an empty-interface static component |
+| Version 2 component referenced by Version 1 | Rejected as an unsupported newer component source |
+
+Version semantics are selected per source unit and reference edge, not from
+the entry source or overall link operation. The source unit containing a
+`use` determines that use's grammar; the source unit containing a component
+definition determines that definition body's grammar. Accepting a Version 1
+component from Version 2 does not upgrade, reparse, or reinterpret the Version
+1 source as Version 2. A Version 1 source likewise cannot reference Version 2
+merely because it is reachable from a Version 2 entry.
+
+For example, `{price}` in a Version 1 component remains the
+literal characters `{price}` when used from Version 2, and
+`{{price}}` retains both pairs of braces. The same single-brace
+form inside a Version 2 component is a binding and fails when `price` is not a
+declared prop.
+
+A Version 2 use of a Version 1 component cannot contain arguments because the
+target declares no props. A Version 2 source may itself declare components
+with no props and use them statically.
+
+Unknown Version 2 syntax remains an error. Version 2 does not silently accept
+future parameter kinds, value types, or render-valued inputs.
+
+## Grammar delta
+
+The Version 2 delta is:
+
+```text
+component := component-start prop* existing-component-body component-end
+prop := empty `prop` element
+use := empty `use` element | use-start arg* use-end
+arg := empty `arg` element
+```
+
+Comments and formatting whitespace do not affect the grammar.
+
+Every `prop` is a leading direct child of its `component`. All prop
+declarations must precede the component's `fill`, `stroke`, and render
+children. A `prop` is non-rendering and occupies no layout or painter
+position. A component with no declarations omits `prop` entirely; there is no
+`props` collection wrapper.
+
+Every `arg` is a direct child of `use`. Arguments are typed property children,
+not render children, and occupy no layout or painter position. A use may still
+be self-closing when it supplies no arguments. Non-whitespace character data,
+render children, and every other structural child remain invalid inside
+Version 2 `use`.
+
+The complete list of leading declarations is indexed before bindings on the
+component's own start tag are validated. A component may therefore bind its
+own box or visual attributes to a prop declared immediately inside it.
+
+## Prop names and scope
+
+Prop names use the same compact grammar as component IDs:
+
+```text
+prop-name := [a-z][a-z0-9]*("-"[a-z0-9]+)*
+```
+
+Names are case-sensitive and unique within one component. These names are
+reserved because they belong to component identity or the fixed relationship
+between a use and its caller:
+
+```text
+id href name x y flow grow align hidden
+```
+
+Other ordinary property names, including `width`, `height`, and `fill`, remain
+available as prop names. Explicit `arg` elements keep those component-local
+names separate from the fixed `use` attribute grammar.
+
+A prop's lexical scope is exactly one component definition:
+
+- the component's own scalar attributes;
+- scalar attributes in its paint, stroke, text, and render descendants;
+- direct `text` and `tspan` character content; and
+- argument values on nested uses.
+
+The scope does not enter the referenced component definition. Values cross
+that boundary only through explicit nested arguments. A prop declaration in
+one component never shadows, captures, or implicitly supplies a same-named
+prop in another component.
+
+Bindings are invalid in a scene root outside a component because no prop
+environment exists there.
+
+## Scalar value types
+
+Each prop requires one explicit `type` from this closed initial set:
+
+| Type | Value domain and purpose |
+| ---------- | ------------------------------------------------------------------------- |
+| `string` | XML-decoded Unicode string; the empty string is valid |
+| `boolean` | Exactly `true` or `false` |
+| `number` | Finite Grida XML decimal number |
+| `color` | One value from the existing Grida XML authored-color domain |
+| `enum` | One member of the declaration's explicit `values` set |
+| `resource` | Non-empty authored resource identifier carrying lexical source provenance |
+
+These are value categories, not parallel scene properties. The established
+target property still owns applicability, range, defaults, contradictions,
+and materialization. For example, `number` admits a finite value, while an
+`opacity` binding additionally requires that value to be in `[0, 1]` and a
+`font-size` binding requires it to be positive.
+
+The complete initial injection relation is:
+
+| Prop type | Admissible established scalar sink |
+| ---------- | ----------------------------------------------------------- |
+| `string` | Free-string attribute or direct text-character segment |
+| `boolean` | Boolean-valued attribute |
+| `number` | Numeric branch of a property grammar |
+| `color` | Color-valued attribute, including compact one-solid `fill` |
+| `enum` | Keyword branch of a property grammar accepting every member |
+| `resource` | Resource-identifier attribute |
+
+This table defines category compatibility only. The ordinary target still
+validates the supplied value and cross-property state. A `number` may, for
+example, enter the numeric branch of `width` or `font-weight`; `width` then
+applies its non-negative rule, while `font-weight` requires an integer from 1
+through 1000.
+
+An `enum values="auto"` prop may enter the keyword branch of `width`, and an
+enum whose members are accepted position keywords may enter that branch of
+`x` or `y`. The same prop still cannot represent both a keyword and a number;
+Version 2 defines no number-or-keyword union prop.
+
+There are no implicit string conversions. A `string` cannot supply a number,
+color, enum, resource, path, transform, paint stack, or list merely because
+all XML source is textual. No cross-type conversion is defined.
+
+`enum` requires a non-empty `values` attribute containing unique
+space-separated lowercase kebab-case tokens. Every declared enum member must
+be valid at every binding target of that prop. Its default and every explicit
+argument must be one declared member.
+
+“Valid at a binding target” means the member belongs to that target's lexical
+keyword domain. Constraints involving other authored properties are checked
+after specialization, when the complete ordinary target state is known.
+
+`resource` is distinct from `string` because its lexical source origin affects
+resolution. It may bind only to an established resource-valued property such
+as image `src`.
+
+Compound authored values such as position bindings, corner lists, insets,
+dash arrays, path data, transform programs, ordered paints, and node subtrees
+are not coerced through `string`. Later versions may add explicit types only
+after their value and canonicalization contracts are defined.
+
+This initial type set cannot expose every ordinary property domain. For
+example, `number` may supply the numeric branch of `width`, but it cannot also
+supply `auto`; no initial prop type represents that union. Authors keep such a
+property literal or use a higher-level runtime until a dedicated typed domain
+is specified. A processor must diagnose that limit rather than recommend
+`string` as an escape hatch.
+
+## Prop declarations
+
+`prop` is an empty element with this attribute contract:
+
+| Attribute | Requirement | Meaning |
+| --------- | ----------- | ------------------------------------------- |
+| `name` | Required | Unique component-local prop name |
+| `type` | Required | One scalar type from the closed set above |
+| `default` | Optional | Literal selected when a use omits this prop |
+| `values` | `enum` only | Non-empty set of accepted enum tokens |
+
+A declaration without `default` is required at every use. A declaration with
+`default`, including `default=""` for a string, may be omitted by a caller.
+There is no separate `required` attribute and no implicit default.
+
+Defaults are literals owned by the definition source. They cannot reference
+another prop. This removes declaration-order evaluation, default dependency,
+and default-cycle semantics.
+
+The default is parsed according to the declared type and validated against
+every known binding target. `values` is required for `enum` and invalid for
+every other type. Unknown attributes are errors.
+
+Declaration order is not evaluation order, but it is retained for inspection,
+documentation, and canonical argument ordering. Every declaration must have
+at least one syntactic consumption site in its component: either a scalar
+binding target or a nested `arg value` forwarding binding. A declaration
+default, an argument supplied by an external caller, and the declaration's
+mere presence do not count. Consumption under a hidden node still counts
+because visibility does not change source validity. A prop with no consumption
+site is a source error; accepting it would admit no-op arguments that are
+especially difficult to detect in directly authored or generated source.
+
+## Requiredness, omission, and nullability
+
+Version 2 deliberately has no universal `null` value.
+
+Each effective value records two independent facts:
+
+| Fact at this use | States |
+| --------------------- | ------------------------------------------------------ |
+| Selection | `CalleeDefault` or `Supplied(argument site)` |
+| Ultimate value source | Literal argument or declaration default, plus forwards |
+
+The selection rule is:
+
+```text
+omitted argument + declared default → CalleeDefault
+explicit literal or forwarded arg → Supplied(argument site)
+omitted argument + no default → error
+```
+
+A forwarded outer default is therefore `Supplied` at the callee while its
+ultimate value source remains the outer declaration default. It does not begin
+tracking the callee's default merely because its original literal came from a
+default.
+
+Empty string, `0`, and `false` are ordinary explicit typed values. `auto` and
+`none`, where admitted through an enum and accepted by a target, are domain
+values rather than null.
+
+An explicit argument remains `Supplied` even when its value equals the current
+component default. Removing it would change future behavior if the definition
+default changes. A source-aware processor therefore retains both selection
+and ultimate value source even when materialized scalar values compare equal.
+
+The string `null` is an ordinary string value for a `string` prop. It is
+invalid for the other initial types unless it is explicitly declared as an
+`enum` member; neither case has null semantics.
+
+Version 2 cannot parameterize the presence of an attribute. A binding supplies
+the complete value of an authored scalar occurrence; it cannot remove that
+occurrence. Future nullable props, if justified, must declare nullability and
+use one structural spelling rather than a magic string sentinel. This RFD
+selects no nullable syntax.
+
+## Arguments
+
+`arg` is an empty element with exactly two attributes:
+
+| Attribute | Requirement | Meaning |
+| --------- | ----------- | --------------------------------------------------- |
+| `name` | Required | One prop declared by the referenced component |
+| `value` | Required | A literal or an exact outer-prop forwarding binding |
+
+Argument names are unique within one use. Argument order is not semantic. An
+argument replaces the declaration default; values do not merge.
+
+After the referenced component is known, each literal argument is parsed as
+the target prop's declared type. Unknown arguments, duplicate arguments,
+missing required arguments, and invalid typed literals are errors. No
+best-effort coercion occurs.
+
+At top-level scene scope, `value` must be literal. Inside a component, an exact
+binding forwards an effective outer value. This complete fragment declares
+both the callee and caller interfaces:
+
+```xml
+
+
+
+
+ {label}
+
+
+
+
+
+
+
+
+```
+
+Forwarding resolves in the lexically containing component. It never captures
+a callee prop of the same name. Source and target prop types must match. For
+`enum`, every possible source member must belong to the target prop's `values`
+set. The forwarded value retains its ultimate literal or default origin
+through every hop.
+
+## Binding syntax
+
+After XML decoding, `{prop-name}` denotes a prop binding. The binding replaces
+one complete scalar value:
+
+```xml
+
+
+
+
+
+ {label}
+
+```
+
+In an XML attribute, the complete decoded value is either literal or exactly
+one binding. Attribute interpolation and concatenation are invalid:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+Direct `text` and `tspan` character content may contain literal character
+segments and string-prop binding segments in document order:
+
+```xml
+
+
+ Hello, {person}.
+
+```
+
+Only `string` props may bind into character content. Inserted strings become
+characters; they are never reparsed as XML, `tspan`, entities, bindings, or
+render nodes. An argument whose string value contains `{other}` therefore
+cannot trigger second-order substitution.
+
+Literal braces use doubling after XML decoding:
+
+```xml
+
+
+ Use {{label}} literally; this value is {label}.
+
+```
+
+In this example the first occurrence materializes as literal `{label}` and
+the second is a binding. An unmatched single brace, an empty binding, or a
+binding whose content is not a prop name is an error. Canonical writers double
+literal braces where binding parsing is active.
+
+Brace-heavy text remains direct to author because each literal brace is
+doubled independently:
+
+```xml
+
+
+ {label}: function demo() {{ return {{ ok: true }}; }}
+
+```
+
+Given `label="Example"`, the text materializes as
+`Example: function demo() { return { ok: true }; }`.
+
+Where binding parsing is active, scanning proceeds left to right with this
+precedence:
+
+1. `{{` emits one literal `{`.
+2. `}}` emits one literal `}`.
+3. `{prop-name}` emits one binding token.
+4. Every other single `{` or `}` is invalid.
+
+Substituted characters are appended after this scan and are never scanned
+again. A canonical encoder is the inverse: it doubles every literal brace and
+emits each binding once as `{prop-name}`. This makes adjacent escaped and bound
+segments deterministic.
+
+Binding recognition is controlled exclusively by the declared version of the
+source unit containing the lexical occurrence. The following rules apply only
+inside Version 2 source units. In Version 0 and Version 1 source, braces retain
+their ordinary Grida XML character meaning: `{name}` is not a binding,
+doubled braces have no escape semantics, and a Version 2 linker or writer must
+not scan, collapse, double, or otherwise reinterpret them. Substitution never
+changes the grammar version applied to copied component content.
+
+Within a Version 2 source unit, binding recognition is contextual:
+
+| Version 2 source position | Brace behavior |
+| ------------------------------------------------------- | ---------------------------------------------------------------- |
+| Scalar attribute in a component body or start tag | Exact `{name}` binds; a binding-shaped literal doubles braces |
+| `text` or `tspan` character content in a component | String bindings and doubled literal braces are recognized |
+| Nested `arg value` inside a component | Exact `{name}` forwards; a binding-shaped literal doubles braces |
+| Top-level `arg value` with no enclosing prop scope | Exact `{name}` is an invalid forwarding attempt |
+| Prop `default` and all binding-forbidden metadata | Braces are literal and use only the position's ordinary grammar |
+| Ordinary scene content outside any component definition | Braces are literal |
+
+For example, `default="{label}"` on a `string` prop is the literal string
+`{label}` because defaults never bind. A top-level string argument that must
+literally contain `{label}` uses `value="{{label}}"`; the single-brace form is
+rejected as a forwarding reference with no enclosing prop scope.
+
+Binding recognition applies to parsed XML source, not to already-decoded
+literal values supplied by an authoring operation. When plain text or an
+existing literal scene value is inserted into a binding-active component
+position, the authoring operation must encode every literal `{` as `{{` and
+every literal `}` as `}}` before writing source. It must not infer a prop
+binding from literal characters.
+
+Extracting an ordinary scene subtree into a component follows the same rule
+and preserves its decoded literal values. Copying structured source that
+already contains prop bindings is different: the operation must preserve a
+compatible component scope, explicitly rebind the references, or specialize
+them to concrete literals. Raw XML paste remains source and is parsed
+normally.
+
+Consequently, moving literal `{status}` into a Version 2
+component canonically writes `{{status}}`, while deliberately
+authoring `{status}` inside that component creates a binding.
+
+Binding is permitted only at an established scalar value sink whose value
+category accepts the prop type. It may customize ordinary attributes and
+scalar leaves inside structured paint, stroke, gradient, and text properties.
+It cannot replace an element name, property subtree, ordered list, or render
+subtree.
+
+Bindings are forbidden in:
+
+- `grida version`;
+- component `id`;
+- prop `name`, `type`, `values`, or `default`;
+- argument `name`;
+- use `href`;
+- element and attribute names; and
+- any unrecognized or non-scalar value position.
+
+A prop may bind several compatible sinks. A sink may have either its ordinary
+literal attribute/value or one binding, never both representations at once.
+After substitution, every sink undergoes its ordinary target-specific
+validation. A value that makes another authored property inapplicable is an
+error; the materializer does not drop or repair the conflicting state.
+
+If string substitution makes a `tspan` empty, ordinary `tspan` validation
+fails with argument and binding provenance. An empty `text` remains valid
+according to the ordinary text contract.
+
+## Evaluation and materialization
+
+Parameter evaluation is deterministic and staged:
+
+1. Parse every loaded source unit under its declared exact version.
+2. Index prop declarations and validate names, types, literal defaults,
+ binding references, and static sink compatibility.
+3. Resolve each reachable component reference under the module-linking rules.
+4. For one use, reject duplicate and unknown arguments.
+5. Resolve every explicit literal or forwarded argument to its declared type.
+6. Select the declaration default for each omitted optional prop and report
+ each omitted required prop.
+7. Substitute every binding with its concrete effective typed value.
+8. Validate the resulting ordinary authored properties and their
+ cross-property constraints.
+9. Materialize the existing ordinary container subtree plus provenance and
+ resource manifest.
+10. Resolve layout, text, paths, paints, and resources through the ordinary
+ scene contract.
+
+There is no prop evaluation order because defaults are literals and sibling
+props cannot reference one another. The complete effective environment is
+immutable for one specialization.
+
+Hidden uses and bindings under hidden nodes are still linked, typed, and
+validated. Visibility does not suppress component API, resource-type,
+lexical-origin, or cycle errors. Actual resource lookup and decode follow the
+ordinary render-resource preflight contract; this RFD does not create a
+different visibility rule for resources.
+
+## Resource-valued props
+
+Every resource value retains the origin of the literal that created it:
+
+| Value source | Lexical origin |
+| ------------------------- | ------------------------------------- |
+| Declaration default | Component-definition source |
+| Explicit literal argument | Source containing that use |
+| Forwarded resource value | Original supplying literal, unchanged |
+
+Two identical relative strings from different origins may resolve to
+different resources. Forwarding through an intermediate component never
+rebases the value to that component's source.
+
+For the primary example, `./images/noise.png` resolves relative to
+`presentation.grida.xml` because the caller authored the argument. Had
+`texture` declared a relative default, that default would resolve relative to
+`components/card.grida.xml`.
+
+Materialization retains `(source origin, authored identifier)` until resource
+resolution or lowers it to an equivalent collision-free runtime key while
+preserving the authored string. Only materialized resource occurrences enter
+the resource manifest; a value that is merely forwarded has no additional
+resource occurrence at the forwarding site. Resource I/O does not occur in
+the painter.
+
+## Provenance and editing
+
+The retained source program distinguishes:
+
+- prop declaration and declared type;
+- declaration default, when present;
+- `CalleeDefault` versus `Supplied` selection at each use;
+- argument site, when supplied;
+- ultimate literal or declaration-default value source;
+- forwarding chain, when forwarded;
+- literal lexical origin;
+- every binding target;
+- containing component and ordered use chain; and
+- materialized node occurrence, when applicable.
+
+Materialization may erase those distinctions from the ordinary render tree,
+but a component-aware model and canonical source writer must not. Changing a
+declaration default affects uses that omit the argument. An explicit argument
+equal to the former default remains pinned and unchanged.
+
+This RFD adds no durable source-node, use-occurrence, or descendant identity.
+Prop provenance uses the source and use boundaries already required by the
+module contract; it does not pretend a runtime node handle is durable authored
+identity.
+
+Version 4 subsequently adds explicit render-member and use IDs through the
+[durable-addressing RFD](./grida-xml-addressing). It does not reinterpret a
+Version 2 binding, source span, or materialized occurrence as identity.
+
+## Cycles and processing limits
+
+Scalar props cannot change `href`, element names, or structure. They therefore
+add no edges to the Version 1 component-expansion graph and do not alter its
+cycle rule. A cycle is rejected before materialized node allocation even when
+the uses or bindings involved are hidden.
+
+Scalar forwarding may increase specialization work but cannot make an
+acyclic definition graph recursive. Processing environments may extend their
+declared expansion limits to include argument bytes, prop count, binding
+count, and specialization work. Exceeding a limit is an explicit resource
+failure, never permission to truncate or drop arguments.
+
+## Source preservation and canonical writing
+
+A source-preserving writer writes props, args, and bindings rather than their
+materialized values or expanded node copies.
+
+A canonical writer for a Version 2 source unit is defined only after every
+`use` whose arguments it normalizes has a successfully linked target
+interface. The following rules apply to that Version 2 unit only; they do not
+upgrade or rewrite linked Version 0 or Version 1 units:
+
+- emits `grida version="2"` for that Version 2 source unit;
+- writes direct leading `prop` elements with attribute order `name`, `type`,
+ `values`, then `default` where applicable;
+- preserves prop declaration order;
+- emits `enum values` members in ascending ASCII lexical order; a
+ source-preserving same-location writeback may retain authored member order;
+- writes explicit args in the linked target declaration order; if that target
+ interface is outside the available link closure, canonical writing fails,
+ while source-preserving write-back retains authored argument order;
+- does not emit an argument for a `CalleeDefault`-selected prop;
+- preserves an explicit argument even when it equals the declaration default;
+- preserves forwarding bindings rather than replacing them with effective
+ literals;
+- emits canonical lexical forms for typed literals;
+- emits exact `{name}` bindings and doubles literal braces where binding
+ parsing is active;
+- XML-escapes strings normally and uses character references when tabs or
+ line breaks must survive XML attribute normalization;
+- preserves authored resource identifiers and their source association; and
+- writes the component/use source program, never materialized clone trees.
+
+A canonical multi-file writer applies the appropriate version-specific writer
+to each source unit. It requires the complete library-validation closure for
+the program being written. In a mixed program, every Version 2 source and
+Version 1 dependency retains its declared version. A partial-link operation
+may perform source-preserving write-back, but it must not claim complete
+canonicalization.
+
+Canonical print, parse, link, and materialize must be a semantic fixpoint for
+selection, ultimate value source, forwarding, binding targets, resource
+origins, and the resulting ordinary scene.
+
+A conversion to a target without live component source must flatten with
+declared loss or reject, following the module RFD. A concrete materialized
+scene without the retained source program cannot reconstruct prop declarations
+or determine the selection and ultimate value source of effective values.
+
+## Validation and diagnostics
+
+Parameter failures are source, link, or specialization errors rather than
+render fallbacks. Useful diagnostics include:
+
+- `duplicate prop "accent" in component "button"`;
+- `prop name "x" is reserved by the use instance boundary; choose a component-local API name`;
+- ` declarations must precede fill, stroke, and render children`;
+- `prop "accent" in component "button" has no binding or forwarding site`;
+- `prop "fit" type="enum" requires a non-empty values attribute`;
+- `default "purple" is not a valid color for prop "accent"`;
+- `binding "accent" is not declared by component "button"`;
+- `binding "lable" is not declared by component "button"; available: label`;
+- `prop "label" has type string and cannot bind to opacity`;
+- `argument "label" is required by component "button"`;
+- `unknown argument "tone" for component "button"; available: accent, label`;
+- `duplicate argument "label" for component "button"`;
+- `enum prop "fit" cannot forward to "image-fit"; source member "fill" is not accepted by the target`;
+- `argument "opacity" is 1.4; bound target opacity requires a value in [0, 1]`;
+- `href is not parameterizable; component references must remain statically linkable`;
+- `attribute bindings must replace the complete value; interpolation in src is invalid`;
+- `unescaped "{" in component text; write "{{" for a literal brace or "{prop-name}" for a binding`;
+- `prop binding "label" has no enclosing component scope`;
+- `"{label}" in top-level arg value cannot forward without an enclosing component scope; write "{{label}}" for literal text`;
+- `version 1 use cannot contain arg; component parameters require version 2`;
+- `version 2 use accepts only arg children; render assignments require version 3`;
+- `resource "./noise.png" was supplied at presentation.grida.xml:18 and could not be resolved`.
+
+A nested failure identifies the declaration, argument or default that supplied
+the value, binding target, immediate use, referenced definition, and transitive
+use chain. It must not be reported only against a transient materialized node.
+
+## Version 3 named slot projection
+
+The [Grida XML component slots](./grida-xml-component-slots) RFD proposes the
+next exact language delta. Version 3 declares empty named insertion markers as
+`` and supplies direct render roots under `use` with the
+contextual `slot="…"` relationship. Matching roots splice directly at the
+marker while retaining caller lexical ownership. That RFD owns layout,
+painter order, clipping, resource origin, cycles, provenance, and
+source-writing behavior for projection.
+
+Version 2 `use` children nevertheless remain exactly `arg*`. A Version 2
+reader rejects slot declarations, assignment attributes, and render children;
+it never reparses a scalar string as XML or render content. Version 3 inherits
+the scalar contract in this RFD rather than changing its types, binding syntax,
+or evaluation rules.
+
+## Runtime boundary
+
+The following remain appropriate for a separate runtime or higher-level
+language:
+
+- computed and derived values;
+- conditions and repetition;
+- state and event handling;
+- asynchronous data and resource discovery;
+- functions and user code; and
+- runtime-changing component selection.
+
+Such a runtime may produce versioned Grida XML source or a concrete ordinary
+scene. The Grida renderer continues to accept the latter without requiring
+component support. Conversely, static props and slots do not require that
+runtime merely to render or edit an authored `.grida.xml` program.
+
+## Current model compatibility
+
+The existing ordinary scene and renderer need no component, prop, or argument
+node variants to materialize this proposal. Each use still produces distinct
+ordinary node occurrences and the renderer remains component-blind.
+
+Component-aware inspection and editing require the retained linked source,
+use boundary, prop provenance, and materialized occurrence map. The packed
+archive cannot claim live parameterized-component round-trip until it gains an
+explicit source-program or definition/instance layer. Until then, a converter
+must flatten with declared loss or reject; it must not store parameter source
+in an XML-only side table while claiming complete archive support.
+
+An implementation may cache typed defaults, linked definitions, or equivalent
+specializations. Caching is conforming only when distinct uses retain distinct
+observable occurrence identity, source provenance, resource resolution, and
+diagnostics.
+
+## Considered alternatives
+
+### External runtime owns all component parameters — rejected as canonical
+
+External producers remain supported, but requiring one for static defaults and
+arguments would make Grida XML unable to preserve or edit its own component
+API. Basic deterministic parameterization belongs to the source linker.
+
+### Component, prop, or use nodes in the render scene — rejected
+
+The renderer needs concrete geometry, text, layout, and paint values. Adding
+unresolved source constructs to that scene would spread component identity and
+evaluation through layout, querying, caching, and painting without changing
+the final visual model.
+
+### Arbitrary attributes directly on `use` — rejected
+
+```xml
+
+```
+
+Component-dependent attributes collide with fixed instance relationship
+attributes and make the grammar impossible to validate without resolving the
+component. Explicit `arg` children keep the fixed `use` vocabulary stable.
+
+### Untyped lexical props — rejected
+
+Treating every prop as a string would create a parallel coercion layer and
+weaken diagnostics, especially for colors, numbers, booleans, enums, and
+resource origins. The small type set identifies value categories while
+ordinary target properties retain their existing constraints.
+
+### `` and `` collection wrappers — rejected
+
+Direct leading `prop` and direct `arg` children already have unambiguous
+context, match the language's existing leading typed-property topology, and
+avoid collection-only tags.
+
+### Generic `` elements — rejected
+
+Binding elements are verbose, interfere with established property-child and
+render-child order, and cannot naturally represent text segments. Exact brace
+bindings use one reference form across scalar attributes, text, and nested
+argument forwarding.
+
+### General interpolation or expression attributes — rejected
+
+Attribute bindings replace one complete value. Functions, paths, arithmetic,
+fallback operators, and concatenated attributes would create an expression
+runtime and make static target validation weaker. Text alone permits ordered
+literal and string-binding segments because its authored value is already a
+character sequence.
+
+### Defaults that reference other props — rejected
+
+Dependent defaults introduce evaluation order, a second dependency graph, and
+cycles without being necessary for initial static customization. Higher-level
+runtimes may compute related values explicitly.
+
+### Nullable props in Version 2 — deferred
+
+Requiredness and defaults cover the initial authoring need without confusing
+omission, null, empty, and target defaults. A later nullable design must prove
+that its target model has a real absent state and define structural syntax;
+`value="null"` will not become a universal sentinel.
+
+### Scalar props and render-valued slots in one version — deferred
+
+Scalar substitution does not change tree topology. Render-valued input does,
+and therefore requires independent decisions about layout, painter order,
+fallback, identity, ownership, cycles, and canonical writing. Combining them
+would make the small scalar contract depend on the independent structural
+policy now owned by the Version 3 component-slot RFD.
+
+## Proposed conformance requirements
+
+A conforming implementation of this proposal:
+
+1. **MUST** implement Version 2 only as the Version 1 module model plus the
+ parameterization delta defined here.
+2. **MUST** require leading direct prop declarations with unique valid names
+ and one declared scalar type.
+3. **MUST** retain `CalleeDefault` versus `Supplied` selection independently
+ from ultimate value source, including forwarded defaults and an explicit
+ argument equal to the callee default.
+4. **MUST** reject unknown, duplicate, missing, mistyped, and target-invalid
+ arguments with component and use provenance.
+5. **MUST** permit bindings only in compatible established scalar sinks and
+ validate the resulting ordinary target properties after substitution.
+6. **MUST NOT** interpret brace bindings as expressions, rescan substituted
+ strings, or parse strings as XML or render content.
+7. **MUST** resolve forwarding in the lexical outer component and preserve the
+ original value origin through every hop.
+8. **MUST** resolve relative resource values from the source of their supplying
+ literal, not the binding target or final entry source.
+9. **MUST** materialize a fully concrete ordinary scene before layout, text,
+ path, paint, or render resolution.
+10. **MUST NOT** add component, prop, argument, binding, or null variants to
+ the observable render-scene contract merely to support this source syntax.
+11. **MUST** preserve prop, argument, binding, forwarding, and use provenance
+ in the linked source program and canonical writer.
+12. **MUST NOT** remove an explicit argument because it currently equals a
+ declaration default.
+13. **MUST** keep Version 2 use content restricted to typed args and reject
+ render children or Version 3 slot syntax.
+14. **MUST** retain the Version 1 cycle, expansion, instance-independence, and
+ source-preservation contracts.
+15. **MUST** accept Version 1 components as empty-interface static definitions
+ during a Version 2 operation, but **MUST NOT** permit arguments on such
+ uses.
+16. **MUST** select brace and binding grammar from the containing source unit's
+ declared version. A Version 2 operation that links a Version 1 component
+ **MUST** preserve Version 1 braces literally and **MUST NOT** manufacture
+ bindings or Version 2 escaping inside that source.
+17. **MUST** preserve authored argument order and declared source versions
+ during source-preserving write-back when target interfaces are outside the
+ link closure.
+18. **MUST NOT** claim canonical multi-file output without completing the full
+ library-validation closure required for target-dependent normalization.
+19. **MUST** canonicalize each source unit under its own declared version and
+ **MUST NOT** upgrade a Version 1 dependency because it was reached from
+ Version 2.
+20. **MUST** reject a prop declaration that has no scalar binding or argument
+ forwarding site in its component.
+21. **MAY** cache immutable declarations or specializations when observable
+ instance behavior, provenance, and diagnostics remain unchanged.
diff --git a/docs/wg/format/grida-xml-component-slots.md b/docs/wg/format/grida-xml-component-slots.md
new file mode 100644
index 0000000000..9d96c3b43c
--- /dev/null
+++ b/docs/wg/format/grida-xml-component-slots.md
@@ -0,0 +1,590 @@
+---
+title: "Grida XML component slots"
+description: "Open RFD proposing exact Version 3 named render-slot projection for Grida XML components while preserving caller lexical ownership and an ordinary materialized scene."
+keywords:
+ - grida xml
+ - component slots
+ - named slots
+ - content projection
+ - static materialization
+tags:
+ - internal
+ - wg
+ - format-schema
+ - canvas
+ - authoring
+ - architecture
+ - resources
+ - scene-graph
+format: md
+---
+
+# Grida XML component slots
+
+**Status:** Open RFD — selected Version 3 named direct-slot projection delta
+with a proving implementation of exact source parsing, linking, direct
+projection, projection provenance, ordinary-scene materialization, and
+local-file rendering. Canonical source writers and complete-library validation
+remain unimplemented. None of the slot vocabulary introduced by this RFD is
+valid Version 0, Version 1, or Version 2 syntax.
+
+**Companion specifications:** [Grida XML](./grida-xml) owns ordinary authored
+scene values. [Grida XML modules and static component
+reuse](./grida-xml-modules) owns source units, boxed component definitions,
+`use`, linking, materialization, and component provenance. [Grida XML component
+parameters](./grida-xml-component-parameters) owns Version 2 scalar props,
+arguments, bindings, and lexical scalar scope. This RFD specifies only the
+Version 3 render-slot delta. [Grida XML durable
+addressing](./grida-xml-addressing) owns the later Version 4 member/use
+identity and occurrence-path delta.
+
+## Decision summary
+
+Version 3 components declare named insertion points inline. A Version 3 use
+supplies zero or more direct render roots to those names:
+
+```xml
+
+
+
+ {label}
+
+
+
+
+
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
+```
+
+The proposal makes these choices:
+
+| Question | Version 3 answer |
+| ------------------------ | -------------------------------------------------- |
+| Declaration | Empty inline `` |
+| Assignment | Direct render root under `use` with `slot="media"` |
+| Names | Required, case-sensitive lowercase kebab-case |
+| Cardinality | Zero, one, or many roots per declared slot |
+| Absence | Zero roots erase the marker |
+| Topology | Direct splicing; no implicit wrapper |
+| Default slot | None |
+| Fallback or requiredness | None |
+| Explicit empty | None; omission is the empty projection |
+| Lexical ownership | Caller source |
+| Structural placement | Slot declaration site |
+| Rendering representation | Existing ordinary scene nodes only |
+
+“Static” means the target component, declared slot interface, supplied roots,
+and resulting topology are determined during linking and materialization.
+Version 3 slots do not add runtime-changing selection, conditions, or
+reactivity.
+
+## Why Grida owns projection above rendering
+
+Scalar props specialize values but cannot carry typed render trees. Encoding a
+subtree as a string would require a second parse, erase node typing and source
+locations, and make resource and binding ownership ambiguous. Copying the
+component shell at every call site would instead discard the reusable
+definition.
+
+Slot projection is therefore retained component-source intent. The linked
+source program preserves declarations, assignments, lexical owners, and use
+boundaries. Materialization removes that vocabulary and produces the ordinary
+scene already consumed by layout and rendering. A slot is not a new geometry,
+box, paint, or runtime node kind.
+
+## Goals
+
+Version 3 projection must:
+
+- accept caller-authored typed render roots without parsing strings as XML;
+- make every destination explicit and named;
+- preserve caller resource bases, scalar scope, nested references, and source
+ provenance;
+- give projected roots the exact parent layout, local coordinate space,
+ clipping ancestry, and painter position selected by the definition;
+- preserve one ordinary materialized scene with no slot vocabulary;
+- reject unknown destinations and invalid projected relationships before
+ ordinary scene resolution;
+- include assigned content in finite-expansion and cycle validation; and
+- remain predictable for direct human and language-model authoring.
+
+## Non-goals
+
+Version 3 does not define:
+
+- unnamed or default slots;
+- fallback children, required slots, or an explicit-empty sentinel;
+- a slot assignment wrapper or boxed slot node;
+- render nodes as scalar prop values;
+- deep descendant overrides or patches;
+- conditions, loops, reactive content, or dynamic component references;
+- durable use, assignment-root, or projected-descendant identity;
+- component variants or inheritance; or
+- live-component persistence in the packed archive.
+
+An author may deliberately assign an ordinary `group` or `container`, but that
+node retains its ordinary scene meaning. It is not an implicit slot wrapper.
+
+## Vocabulary
+
+| Term | Meaning |
+| ------------------------- | ------------------------------------------------------------------------------------------ |
+| **Slot declaration** | One named, non-rendering insertion marker in a component body |
+| **Assignment root** | One caller-authored direct render child of `use` carrying `slot` |
+| **Projection** | Replacement of one slot marker by its matching assignment roots |
+| **Slot identity** | Qualified component identity plus component-local slot name |
+| **Projection occurrence** | One declared slot as observed through one concrete use occurrence |
+| **Lexical owner** | Source context that owns parsing, bindings, resources, references, and authored provenance |
+| **Structural parent** | Definition-owned ordinary node that receives projected roots |
+
+Component identity, use occurrence, slot identity, assignment root,
+materialized root occurrence, and runtime node handle are different facts.
+This RFD does not substitute one for another.
+
+## Version relationship
+
+Version 3 inherits the complete Version 2 component-parameter model and adds
+only the syntax and semantics in this RFD.
+
+| Caller source | Version 1 target | Version 2 target | Version 3 target |
+| ------------- | ---------------- | ---------------- | -------------------------------------- |
+| Version 1 | Static use | Rejected | Rejected |
+| Version 2 | Static use | Scalar arguments | Rejected |
+| Version 3 | Static use | Scalar arguments | Scalar arguments and named assignments |
+
+Direct render assignments are valid only on a Version 3 caller-to-Version 3
+target edge. Older callers reject a Version 3 target even when that target
+happens to declare no props or slots. A Version 3 caller may link a Version 1
+component only as an empty-interface static definition and a Version 2
+component only through its declared scalar interface; neither older target
+may receive render assignments.
+
+Version semantics are selected per source unit and reference edge. Reachability
+from a Version 3 entry does not upgrade, reparse, or reinterpret an older
+source. A Version 2 component that references a Version 3 component still
+fails on that Version 2 edge.
+
+## Grammar delta
+
+The Version 3 delta is:
+
+```text
+component-render-item := existing-render-root | slot
+slot := empty `slot` element with exactly `name`
+use := empty-use | use-start arg* assignment-root* use-end
+assignment-root := render-root carrying contextual `slot`
+slot-name := [a-z][a-z0-9]*("-"[a-z0-9]+)*
+```
+
+Comments and formatting whitespace do not affect the grammar. An existing
+render root includes an ordinary render element or a nested component `use`.
+A slot declaration is a source marker, not a render root, and therefore cannot
+itself be supplied as an assignment root.
+
+Every `arg` remains the Version 2 typed property child. All arguments precede
+all assignment roots. Once the first assignment root begins, a later `arg` is
+invalid. Non-whitespace character data and every other structural child remain
+invalid inside `use`.
+
+## Slot declarations
+
+`slot` is an empty element with exactly one attribute:
+
+| Attribute | Requirement | Meaning |
+| --------- | ----------- | -------------------------------- |
+| `name` | Required | Component-local destination name |
+
+The name is case-sensitive and uses the same lowercase kebab-case lexical
+grammar as component IDs and prop names. Slot names are unique across one
+complete lexical component body. The scan does not enter a referenced
+component definition. Slot names and prop names occupy distinct namespaces;
+the same spelling does not alias one to the other.
+
+A slot declaration is valid only when:
+
+- its containing source declares `version="3"`;
+- it is lexically enclosed by one component definition; and
+- it occupies a position where an ordinary render child would be valid.
+
+It may therefore appear directly in the component's container-like child list
+or under a render descendant that accepts children. It is invalid in a scene
+root, `text`, `tspan`, `fill`, `stroke`, `gradient`, or any other typed property
+subtree. It is also invalid as a direct child of `use`, whose render roots are
+assignments rather than declaration sites.
+
+The declaration accepts no fallback children, character data, relationship
+attributes, visual properties, or `required` state. Declaring a slot does not
+make supplying it mandatory.
+
+## Assignment roots
+
+Every direct render root under a Version 3 `use` requires one `slot` attribute:
+
+```xml
+
+
+
+
+```
+
+The `slot` attribute is contextual source relationship metadata. It selects a
+declaration on the linked target. It is not an ordinary render property and is
+invalid on a node merely because that node appears elsewhere. A nested node
+may carry `slot` only when it is itself a direct assignment root of a nested
+Version 3 `use`.
+
+One root assigns to exactly one name. Repeated roots naming the same slot are
+valid and are not duplicate assignments. Their direct-child order is their
+projection order. A missing, empty, malformed, or misplaced `slot` attribute
+is a source error. A syntactically valid name not declared by the linked target
+is a link error.
+
+A use may supply no roots even when its target declares slots. Omission is the
+only empty-projection spelling. A hidden assigned root is still an assignment:
+it is retained, linked, validated, and materialized under the ordinary hidden
+contract rather than treated as absence.
+
+## Projection semantics
+
+For a slot named `s`, take the subsequence of the use's direct assignment roots
+whose `slot` value is `s`, preserving caller order. Replace the declaration at
+its exact position in the definition parent's ordered child list:
+
+| Matching roots | Replacement at the marker |
+| -------------- | --------------------------------------------------- |
+| Zero | Nothing; the marker disappears |
+| One | That ordinary root |
+| Many | All matching roots as consecutive ordinary siblings |
+
+The marker contributes no box, layout position, flex position, gap, clipping
+boundary, painter operation, or materialized node. The assignment `slot`
+attribute is consumed by projection and does not appear on the ordinary root.
+
+Each projected root participates independently in the destination parent's
+ordinary child contract. Under flex layout, multiple roots consume multiple
+flex positions and zero roots consume none. In free placement, each root's
+bindings resolve in the destination parent's local box. The slot does not
+reserve space when empty and does not group several roots into one layout
+child.
+
+Definition order determines order across distinct slot declarations. Caller
+order determines order among roots assigned to one slot. Assignment roots for
+different names have no cross-slot painter ordering effect because the
+definition owns their destination positions. Their authored direct-child order
+is nevertheless retained for inspection and source writing.
+
+The destination parent and declaration position determine:
+
+- the local coordinate space;
+- flex or free-position relationship rules;
+- ordinary ancestor transforms, opacity, and clipping; and
+- painter position relative to definition-owned siblings.
+
+Parent-dependent relationship validation occurs after the target and slot
+parent are known. For example, a projected root carrying `grow` requires an
+in-flow position under a flex destination parent; a root projected elsewhere
+cannot retain `grow` as dormant state.
+
+## Lexical ownership
+
+Projection changes structural parenthood without changing lexical ownership:
+
+| Fact | Owner after projection |
+| ------------------------------------------------------- | -------------------------------------------- |
+| Element grammar and source version | Caller source unit |
+| Scalar binding scope | Lexically enclosing caller component, if any |
+| Resource base | Source containing the supplying literal |
+| Nested `use href` base | Caller source unit |
+| Authored node provenance | Caller assignment site |
+| Destination parent and insertion index | Slot declaration |
+| Parent layout, local coordinates, and clipping ancestry | Definition structure |
+| Callee prop environment | Definition-owned content only |
+
+Assigned content inside an outer component binds to that outer component's
+props. It never captures a same-named prop declared by the slot-owning callee.
+At top-level scene scope it has no prop environment. A scalar string remains a
+scalar string and is never reparsed as assigned XML.
+
+The assignment root and every literal in its subtree retain the source unit in
+which they were authored. Projection never rebases a relative image or nested
+component reference to the callee source. A nested use resolves its `href`
+against the caller source exactly as it did before projection.
+
+## Linking, cycles, and finite expansion
+
+Version 3 extends component-expansion validation to the assigned subtrees that
+become reachable through projection. Expansion follows:
+
+- every definition-owned nested use in the ordinary component body; and
+- every nested use in each supplied assignment subtree at its projected
+ position.
+
+Cycle ownership follows lexical component definitions, not the temporary
+structural parent created by projection. A nested use in assigned content
+contributes an edge from the component definition that lexically owns that
+assignment, when one exists. If that edge returns to a component already being
+expanded through the caller's definition chain, the program is recursive and
+invalid.
+
+A top-level scene is not a component definition. It may therefore explicitly
+nest a second use of the same component in an outer use's assignment when the
+authored tree is finite—for example, an outer card whose assigned content is
+one inner card with no further assignment. By contrast, component `outer`
+cannot use `card` and assign a use of `outer` into `card`; that assignment is
+lexically owned by `outer` and closes a recursive definition cycle. A useful
+diagnostic reports the lexical owner, component/use chain, and projection edge
+through which recursion was reached.
+
+Hidden definitions, uses, slot parents, and assignment roots remain in link
+and cycle validation. Visibility cannot make an otherwise recursive source
+finite. Unused exported components remain governed by the module RFD's entry
+closure versus complete-library-validation distinction.
+
+Acyclic projected content may still expand rapidly. Declared processing limits
+may account for assignment roots, projected nodes, reference depth, and total
+expansion work. Exceeding a limit is an explicit resource-limit failure, never
+permission to truncate a slot or keep only some assigned roots.
+
+## Provenance and editing
+
+For every declared slot observed through every materialized component use, the
+retained component-aware product records:
+
+- the target component identity and containing use chain;
+- the slot name and declaration source location;
+- the receiving use occurrence;
+- every assignment root and its caller source location in order; and
+- the resulting ordinary root occurrence for each assignment.
+
+This projection occurrence exists even when the ordered assignment and
+materialized-root lists are empty. Absence of ordinary roots cannot reconstruct
+which empty slot declaration was observed or where it came from.
+
+The ordinary scene contains neither slot declarations nor assignment
+attributes, so it cannot reconstruct this map by itself. Component-aware
+inspection and editing require the retained source and provenance. Editing a
+slot declaration changes definition topology for every use; editing an
+assignment root changes only that caller's supplied content.
+
+Version 3 introduces no durable use, slot-occurrence, assignment-root, or
+projected-descendant ID. Source locations, child indices, and transient runtime
+handles must not be presented as durable authored identity.
+
+Version 4 subsequently requires explicit render-member and use IDs. During
+slot projection it retains the assignment root's caller owner and appends the
+receiving use to its durable occurrence path; it does not promote a Version 3
+source location or child index into identity. See [Grida XML durable
+addressing](./grida-xml-addressing).
+
+## Source preservation and canonical writing
+
+A source-preserving writer writes slot declarations and assignment roots rather
+than their projected ordinary copies. It preserves each source unit's declared
+version, lexical resource association, slot declaration position, use
+boundary, and assignment-root order.
+
+A canonical writer for a Version 3 source unit requires every target interface
+needed to validate and normalize that unit's uses. It:
+
+- emits `grida version="3"` for that source unit;
+- writes each empty declaration as `` at its definition-tree
+ position;
+- writes Version 2 `arg` children before assignment roots and retains the
+ established target-declaration ordering for args;
+- writes every assignment root directly under its use with exactly one `slot`
+ relationship attribute;
+- preserves the complete authored order of assignment roots, including roots
+ naming different slots;
+- represents an empty projection only by omitting assignment roots for that
+ name;
+- preserves caller-authored references and resources with their caller source
+ association; and
+- writes component/use source, never the materialized clone tree.
+
+If a target interface is outside the available link closure, source-preserving
+same-location write-back may retain the unresolved use, but complete canonical
+writing fails. It must not guess slot names, drop assignments, or insert a
+wrapper. A mixed-version writer canonicalizes each unit under its own declared
+version and never upgrades an older dependency because it was reached from
+Version 3.
+
+Flattening into Draft 0 or another ordinary-only target is an explicit lossy
+conversion. It removes declarations, assignment relationships, component
+editing propagation, and projection provenance. Such a converter must complete
+the ordinary tree and correctly preserve or rebase caller-owned resources, or
+reject the target.
+
+## Validation and diagnostics
+
+Slot failures remain distinct from ordinary rendering failures:
+
+| Failure | Category |
+| --------------------------------------------------------- | ---------------------------- |
+| Malformed, misplaced, or duplicate declaration | Source validation |
+| Missing or misplaced assignment `slot` attribute | Source validation |
+| `arg` after an assignment root | Source validation |
+| Unknown slot name | Link validation |
+| Assignment supplied to a Version 1 or Version 2 target | Link validation |
+| Invalid projected parent relationship | Projection validation |
+| Recursive lexical component edge through assigned content | Link validation |
+| Ordinary geometry, layout, paint, or resource failure | Existing downstream category |
+
+Useful diagnostics include:
+
+- ` requires exactly name="…" using lowercase kebab-case`;
+- `duplicate slot "media" in component "card"`;
+- ` is valid only at a render-child position inside a Version 3 component`;
+- `direct render child of requires slot="…"`;
+- `arg must precede every render assignment inside `;
+- `slot attribute is contextual to a direct Version 3 use assignment; it is not an ordinary rect property`;
+- `slot "thumbnail" is not declared by component "card"; available: actions, media`;
+- `render assignments require a Version 3 target; component "card" declares version 2`;
+- `projected root uses grow, but slot "media" is not under an in-flow flex position`; and
+- `component cycle through slot "body": a#outer → b#card → a#outer`.
+
+A projected descendant failure identifies the caller assignment root, slot
+declaration, receiving use, referenced component, and transitive use chain. It
+must not be reported only against a transient materialized node.
+
+## Current model compatibility
+
+The ordinary scene and renderer require no component, slot, or assignment node
+variant. Projection produces ordinary child sequences before layout, text,
+path, paint, and resource resolution. The renderer remains component-blind.
+
+The retained source program and projection provenance are nevertheless
+required for canonical source writing, linked editing, lexical resource
+attribution, and an observable empty projection. A packed target without a
+live source-program or definition/instance layer must flatten with declared
+loss or reject; an XML-only side table cannot make copied ordinary nodes a
+lossless live-component round-trip.
+
+## Considered alternatives
+
+### Direct named assignment — accepted
+
+`` makes the destination visible at the definition site,
+while `slot="media"` keeps each supplied root self-describing. Direct roots
+preserve their actual node kinds and avoid a collection-only envelope.
+
+### Scalar XML or string props — rejected
+
+Strings cannot carry typed render trees without reparsing. Reparsing would
+erase source boundaries and make resource bases, scalar scope, and diagnostics
+ambiguous.
+
+### A `` assignment wrapper — rejected
+
+A wrapper would add structure with no independent scene meaning and obscure
+the actual assigned root. Authors who need one real grouping node can assign an
+ordinary `group` or `container` explicitly.
+
+### Unnamed or default children — rejected for Version 3
+
+Implicit children create an implicit destination and a second assignment
+spelling once named slots exist. Version 3 requires every marker and every root
+to name the relationship.
+
+### Fallback, required, or explicit-empty slots — deferred
+
+These are absence-policy features independent from the initial topology.
+Version 3 has one rule: omission yields an empty projection.
+
+### A boxed slot node in the ordinary scene — rejected
+
+A box would change layout, coordinate, clipping, and painter semantics. Direct
+splicing lets the existing destination parent own those rules without adding a
+renderer concept.
+
+### Callee-scoped assigned content — rejected
+
+Capturing callee props or rebasing resources to the definition would change
+the meaning of caller-authored content merely because it was projected.
+Lexical ownership remains with the caller.
+
+## Proposed conformance requirements
+
+A conforming implementation of this proposal:
+
+1. **MUST** implement Version 3 only as Version 2 plus the named-slot delta in
+ this RFD.
+2. **MUST** require empty, uniquely named slot declarations at valid
+ render-child positions inside Version 3 components.
+3. **MUST** require Version 3 `use` content to be leading `arg*` followed by
+ direct assignment roots, each carrying exactly one contextual `slot`.
+4. **MUST** treat the assignment `slot` attribute as a source relationship and
+ **MUST NOT** accept it as an ordinary render property.
+5. **MUST** reject unknown slot names and render assignments to Version 1 or
+ Version 2 targets.
+6. **MUST** permit zero, one, or many assignments per declared slot.
+7. **MUST** replace each marker by its matching roots in caller order and
+ preserve definition order across markers.
+8. **MUST NOT** create a wrapper, default slot, fallback subtree, requiredness
+ rule, or explicit-empty representation.
+9. **MUST** validate every projected root against the ordinary relationship
+ rules of the slot's actual parent.
+10. **MUST** preserve caller source version, scalar scope, resource origin,
+ nested-reference base, and authored provenance.
+11. **MUST NOT** expose callee props implicitly to caller-authored assigned
+ content.
+12. **MUST** attribute nested uses in assigned content to their lexical caller
+ component for cycle validation, including hidden content; it **MUST** allow
+ finite same-component nesting authored at top-level scene scope and
+ **MUST** reject a projected edge that returns to an active lexical caller
+ definition.
+13. **MUST** materialize an ordinary scene containing no slot declarations or
+ assignment attributes.
+14. **MUST** retain one projection-provenance occurrence per declared slot and
+ component use, including empty projections.
+15. **MUST** preserve slot declarations, assignment roots, and assignment order
+ during source-preserving writing.
+16. **MUST NOT** serialize expanded ordinary copies as canonical Version 3
+ component source.
+17. **MUST** preserve exact Version 0, Version 1, and Version 2 parsing and link
+ behavior and reject links from older callers to Version 3 definitions.
+18. **MUST** distinguish source, link, projection, scene, and resource failures
+ with both caller and definition provenance.
+19. **MUST NOT** parse a scalar string as XML or render content.
+20. **MAY** impose declared processing limits, but exceeding them must fail
+ explicitly rather than truncate a projection.
diff --git a/docs/wg/format/grida-xml-modules.md b/docs/wg/format/grida-xml-modules.md
new file mode 100644
index 0000000000..fb3f80f0ca
--- /dev/null
+++ b/docs/wg/format/grida-xml-modules.md
@@ -0,0 +1,852 @@
+---
+title: "Grida XML modules and static component reuse"
+description: "Open RFD proposing multi-file Grida XML linking through boxed component definitions and SVG-like use references while preserving ordinary Grida scene semantics."
+keywords:
+ - grida xml
+ - components
+ - multi-file rendering
+ - modules
+ - component reuse
+ - source linking
+tags:
+ - internal
+ - wg
+ - format-schema
+ - canvas
+ - authoring
+ - architecture
+ - resources
+ - scene-graph
+format: md
+---
+
+# Grida XML modules and static component reuse
+
+**Status:** Open RFD — selected proposal with a proving implementation of the
+static Version 1 entry-rendering kernel: source units, local and external
+component references, cycle-safe linking, ordinary-scene materialization,
+provenance, resource origins, and local-file rendering. Explicit export roots,
+complete-library validation, canonical source writers, and a complete
+processing-limit policy remain unimplemented. None of the module/component
+vocabulary introduced by this RFD is valid Draft 0 syntax.
+
+“Static” is deliberate: Version 1 links and repeats exact component definitions.
+Typed scalar props are proposed separately as a Version 2 delta in [Grida XML
+component parameters](./grida-xml-component-parameters). Named direct render
+projection is proposed separately as a Version 3 delta in [Grida XML component
+slots](./grida-xml-component-slots). Deep overrides require still-later design.
+None of that later syntax is silently extensible Version 1 syntax.
+
+[Grida XML durable addressing](./grida-xml-addressing) accepts the next exact
+delta: Version 4 requires IDs on render members and uses and turns the retained
+use chain into a durable occurrence path. It leaves this Version 1 grammar and
+its span-based diagnostic provenance unchanged.
+
+**Companion specifications:** [Grida XML](./grida-xml), [Grida XML component
+parameters](./grida-xml-component-parameters), [Grida XML component
+slots](./grida-xml-component-slots), and [Grida XML durable
+addressing](./grida-xml-addressing).
+
+## Decision summary
+
+This RFD proposes one direct model:
+
+```text
+source unit
+├─ named boxed component definition*
+└─ render root?
+
+component reference = canonical source identity + component ID
+use = one authored instance of that reference
+materialization = ordinary Grida container tree
+```
+
+The corresponding source shape is:
+
+```xml
+
+
+
+
+ OK
+
+
+
+```
+
+```xml
+
+
+
+
+
+
+
+```
+
+The proposal makes these choices:
+
+| Question | Proposed answer |
+| ----------------------- | ---------------------------------------------------------------- |
+| Reference syntax | `` |
+| External import aliases | None in the first version |
+| Component exports | Explicit named exports; no default export |
+| Definitions per file | Zero or more |
+| Scene root per file | Zero or one; required only for a render entry |
+| Component shape | Boxed definition with ordinary `container` semantics |
+| Runtime shape | Equivalent to an independent ordinary container subtree per use |
+| Layout participation | One child position, using the component's declared box |
+| Resource base | The source unit where each resource-valued token was authored |
+| Recursive instantiation | Invalid |
+| Scalar props | Proposed Version 2 delta; invalid in static Version 1 |
+| Render-valued inputs | Proposed Version 3 named-slot delta; invalid in static Version 1 |
+| Deep patches | Deferred until durable component-local identity exists |
+
+## Why this belongs above rendering
+
+Component reuse is source intent. A definition exists once, a use names it,
+and editing the definition affects every use. A renderer ultimately needs a
+concrete scene, but copied render nodes alone cannot recover which file or use
+produced them.
+
+The system therefore has two distinct products:
+
+1. a linked source program that retains files, definitions, uses, authored
+ resource strings, and provenance;
+2. a materialized scene containing ordinary Grida nodes for layout and
+ rendering.
+
+The second is derived from the first. It is not a replacement for it and is
+not the canonical source form.
+
+This boundary lets component bodies continue to use the existing Grida
+primitives, paints, text, and layout. The proposal does not introduce a
+parallel widget renderer, a second layout system, or a new paint abstraction.
+
+## Goals
+
+The first multi-file contract must:
+
+- keep every dependency and instance visible in source;
+- use the same reference spelling for local and external components;
+- preserve one ordinary scene tree after materialization;
+- make every use one predictable boxed position in parent layout and painter
+ order;
+- resolve relative component and image locations against the file where they
+ were authored, never the process working directory;
+- reject missing components, recursive expansion, and invalid materialized
+ geometry before rendering;
+- retain enough provenance to report both the use site and definition site;
+- permit implementations to share immutable definition data without changing
+ observable instance semantics; and
+- preserve component/use source when writing rather than serializing copied
+ descendants as if they had been authored inline.
+
+## Non-goals of the first slice
+
+The first slice does not define:
+
+- an expression language or general data binding;
+- arbitrary attributes whose names are invented by each component;
+- scalar props or named/default slots;
+- deep descendant overrides;
+- component variants, inheritance, private exports, or re-exports;
+- dynamic component references;
+- recursive components;
+- package names, versions, remote-fetch policy, or dependency lockfiles;
+- durable node or instance identifiers;
+- component-aware animation or event state; or
+- a packed archive representation that preserves live instances.
+
+These are not prohibited forever. They require a later exact source version so
+multi-file loading and static reuse can become correct without prematurely
+choosing their identity, typing, or evaluation models.
+
+The scalar subset now has a focused proposal in [Grida XML component
+parameters](./grida-xml-component-parameters). That proposal does not alter
+this Version 1 boundary. The [component-slot
+RFD](./grida-xml-component-slots) separately proposes exact Version 3 named
+direct projection. It likewise does not alter Version 1.
+
+The [durable-addressing RFD](./grida-xml-addressing) separately defines exact
+Version 4 owner/member identity and use-occurrence paths. Version 1 provenance
+remains diagnostic and does not become a generated durable identity.
+
+## Vocabulary
+
+| Term | Meaning |
+| ------------------------ | ---------------------------------------------------------------------- |
+| **Source unit** | One independently located `.grida.xml` source |
+| **Source identity** | The canonical identity supplied by the source-resolution environment |
+| **Component definition** | A named, non-painting source definition with `container` semantics |
+| **Component identity** | The pair `(canonical source identity, component ID)` |
+| **Use** | One authored instance site referring to a component identity |
+| **Link** | Resolve references and validate the transitive component graph |
+| **Materialize** | Produce the concrete ordinary scene observed by layout and rendering |
+| **Provenance** | The source and component/use chain from which materialized intent came |
+
+A future local component alias, runtime node handle, human node label, and
+component identity are different facts. This RFD does not use one as a
+substitute for another.
+
+## Source-unit grammar
+
+The proposed Version 1 envelope contains zero or more component definitions,
+followed by at most one scene root:
+
+```text
+source-unit := component* container?
+```
+
+Comments and formatting whitespace may appear between those elements.
+Component definitions must precede the scene root. No component definition
+may appear inside a render tree or another component.
+
+Definition order is not painter order and does not control lookup. A component
+may reference a component declared later in the same source unit; source order
+is retained only for stable inspection and writing.
+
+A source unit may serve one or both roles:
+
+| Contents | Role |
+| ------------------------------- | ------------------------------------------------------ |
+| Components and one `container` | Renderable entry that also exports reusable components |
+| One or more components, no root | Component library |
+| One `container`, no components | Ordinary single-file scene |
+| Neither | Invalid; the source unit exports and renders nothing |
+
+A host asked to render a source unit requires its one scene root. Referencing
+a component-library source is valid even though that source has no scene root.
+A scene root is never an implicit component export.
+
+Version 0 retains its exact one-container-child contract and rejects every
+element introduced here. A Version 1 linker must not reinterpret a Version 0
+scene root as an unnamed component. Apart from the Version 1 envelope,
+component, and use vocabulary proposed here, ordinary render nodes and their
+properties retain the Grida XML contract they already have. Version 1 remains
+strict current-version syntax: unknown elements, attributes, and structural
+children—including Version 2 parameter syntax—are errors rather than inert
+extension data.
+
+## Component definitions
+
+A component definition has one required `id` and otherwise has the same
+authored box, paint, stroke, clipping, layout, and child grammar as
+`container`:
+
+```xml
+
+ Continue
+
+```
+
+At its definition site, `component` is non-rendering: it has no painter
+position and contributes no scene bounds. At each use, it materializes as one
+ordinary `container` carrying the definition's authored properties and
+children. A component may contain nested `use` children wherever an ordinary
+container may contain render children.
+
+This boxed boundary is deliberate. It gives every instance the same known
+layout kind, including well-defined flex growth and cross-axis alignment, and
+avoids making use-site validity depend on choosing among the incompatible root
+contracts of path, text, line, group, or lens. Reusable primitive content
+remains straightforward: place the primitive inside the component box.
+
+The definition has no visual parent, so `x`, `y`, `flow`, `grow`, and `align`
+are invalid on `component`. A use supplies that caller relationship when the
+definition is instantiated. All other attributes and structural children are
+validated exactly as they would be on `container`; component does not acquire
+a second layout or paint grammar.
+
+Every top-level component is public to references that can resolve its source
+unit. Version 1 has no private export, re-export, or component alias construct.
+
+### Component IDs
+
+`component id` is an exported symbol in one source unit. It is separate from
+the human-readable `name` inherited from `container`, from future durable
+render-node identity, and from materialized runtime handles.
+
+The export `id` is definition metadata and is not copied into the materialized
+container as a render-node ID. Repeated uses receive distinct runtime handles;
+a future durable instance/root identity requires its own authored contract.
+
+The proposed first grammar is a lowercase kebab-case identifier:
+
+```text
+component-id := [a-z][a-z0-9]*("-"[a-z0-9]+)*
+```
+
+IDs are case-sensitive and unique within one source unit. The same ID may
+exist in unrelated source units because its qualified component identity also
+contains the canonical source identity.
+
+## Uses and references
+
+`use` is valid wherever one render-node child is valid. It is invalid inside
+`text`, `tspan`, `fill`, `stroke`, `gradient`, or any other typed property
+subtree.
+
+Every use requires one `href` whose fragment is a component ID:
+
+```xml
+
+
+```
+
+The fragment is always required. These are invalid:
+
+```xml
+
+
+
+```
+
+Version 1 `use` is empty. Character data, render children, `arg`, `content`,
+`fill`, `stroke`, and every other structural child are invalid.
+
+Requiring an explicit component-ID fragment avoids an implicit default export and prevents a
+file from gaining a second canonical reference merely because it happens to
+contain one component today.
+
+After XML entity decoding, the Version 1 lexical form is:
+
+```text
+href := "#" component-id | location "#" component-id
+```
+
+`location` is a non-empty URI reference without a literal fragment or ASCII
+whitespace. A literal `#` in the location must be percent-encoded if the source
+environment supports it; a backslash is invalid. A query may remain part of
+`location` and is interpreted only by the source environment. The component
+fragment itself is not percent-decoded: it must already match the exact ASCII
+`component-id` grammar. Additional literal `#` characters are invalid.
+
+### Reference resolution
+
+For `href="location#id"`:
+
+1. an empty `location` selects the source unit containing the use;
+2. a non-empty relative location resolves against that source unit's base
+ location;
+3. the source-resolution environment returns one immutable link snapshot:
+ bytes, a canonical source identity, and a canonical base location;
+4. the selected source must declare exactly `version="1"` for a Version 1 link;
+5. `id` is looked up only in that source's component-export namespace.
+
+The parser itself performs no file, package, network, or decode operation.
+Allowed schemes, filesystem roots, symlink policy, network access, source-size
+limits, and caching belong to the explicit source-resolution environment. The
+syntax grants none of those capabilities by itself.
+
+Within one link operation, one canonical source identity must always denote
+the same bytes and canonical base. Two lexical locations that map to that
+identity therefore share one component namespace for caching and cycle
+detection, and their nested relative references resolve from the same base.
+An environment that cannot provide that stable tuple must report a source
+resolution failure rather than reuse an ambiguous cache entry. The authored
+`href` remains unchanged in source.
+
+Future language versions may define explicit backward-link rules. Version 1
+does not transitively acquire syntax from a newer component source and does
+not treat a version-0 scene as a component library.
+
+## Instance boundary
+
+A use is a source-level instance boundary. It contributes one materialized
+component container to the caller's child list but introduces no additional
+painted wrapper around that container.
+
+The first slice accepts only properties that belong to the relationship
+between that component and its caller:
+
+| Attribute | Meaning |
+| --------- | -------------------------------------------------------------- |
+| `href` | Required qualified component reference |
+| `name` | Optional human-readable label for this use |
+| `x`, `y` | Non-span free-position binding of the component container |
+| `flow` | Flex participation of the component container |
+| `grow` | Main-axis growth of the component container |
+| `align` | Cross-axis override of the component container |
+| `hidden` | Removes this one instance from layout and painting when `true` |
+
+Applicability remains contextual: `flow`, `grow`, and `align` are valid only
+when the use occupies the corresponding position under a flex container, and
+an in-flow use omits `x` and `y` exactly like an ordinary in-flow node. The
+`x` and `y` values accept start, end, and center pins but not `span`, because
+the component definition owns authored size intent. Flex growth and stretch
+may still assign a different resolved box through the ordinary container
+layout contract without rewriting that intent.
+
+The component definition owns its width, height, constraints, paints, strokes,
+layout, opacity, rotation, flips, clipping, and all other internal intent.
+Those values are not silently overwritten by same-named attributes on `use`.
+Changing them requires editing the component or, after parameterization is
+specified in a later version, using its declared API. Authors can wrap a use
+in an ordinary `group`, `container`, or `lens` when they need additional
+composition or visual transformation without changing the component.
+
+During materialization, `x`, `y`, `flow`, `grow`, and `align` replace the
+component definition's forbidden parent relationship with the values authored
+on the use or their ordinary defaults. They do not replace any definition-owned
+box or visual property.
+
+`hidden="true"` is an instance gate. It suppresses the use even if the
+definition container is active. Omission or `false` does not force a
+definition container that is itself hidden to become visible.
+
+Equivalently, materialized activity is definition activity logically AND the
+inverse of use `hidden`. The relationship fields above are validated after
+linking against the component's known container semantics, and diagnostics
+identify both the use and definition when their combination is invalid.
+
+`name` labels the retained use occurrence. It neither renames the component
+definition nor replaces the materialized container's definition-owned node
+label. A component-aware inspector reads the use label from retained
+provenance; a rendering-only ordinary tree need not carry it in its one node
+label field.
+
+## Layout and painter order
+
+Materialization places the referenced component container at the use's exact
+position in its caller's ordered child list.
+
+- The caller observes one layout child, not the component's children as
+ independent siblings.
+- Under flex layout, the use consumes one flex slot. Its `grow`, `align`, and
+ `flow` values apply to that slot.
+- Free `x` and `y` bindings place the component container in the caller's
+ local box.
+- The materialized container's box is the instance's effective box.
+- The top-level definition paints nothing at its declaration site.
+- The materialized container paints at the use's sibling painter position.
+- Internal children preserve their component-source order and ordinary local
+ coordinate semantics.
+
+An implementation may physically clone each subtree, retain immutable shared
+definition data, or cache a component-specific display result. It is
+conforming only when the observable result is equivalent to each use owning
+an independent ordinary subtree. State, visibility, layout assignment, and
+resource resolution from one use must not leak into another.
+
+The use occurrence remains an observable component boundary even when
+rendering consumes an expanded ordinary tree. Materialized descendants retain
+their containing use in provenance; flattening them without that map is a
+rendering projection, not a complete component-aware model. Selection,
+snapping, hit inspection, isolation, and definition-editing policy belong to
+the consuming editor, which may expose either the instance or its descendants
+without erasing that boundary.
+
+## Linking and materialization
+
+Multi-file rendering requires an explicit stage between parsing and ordinary
+scene resolution:
+
+```text
+entry source + source environment
+ → parse source units
+ → resolve component references
+ → validate the component-expansion graph
+ → materialize one ordinary scene + provenance + resource manifest
+ → resolve layout, text, paths, and paints
+ → render
+```
+
+Every operation begins with the entry source as the same immutable snapshot
+required for dependencies: bytes, canonical source identity, and canonical
+base location. Local component identity and entry-relative `href` and `src`
+values use that entry snapshot rather than an ambient working directory.
+
+Parsing one source unit preserves its component definitions, uses, raw
+resource strings, and local source locations. It does not immediately erase
+them into a concrete scene.
+
+Linking resolves the requested reference closure and validates the qualified
+graph. Materialization then produces the ordinary tree consumed by scene
+resolution. Every materialized node remains attributable to:
+
+- its authored source unit and source location;
+- the component definition, if any, that contains it; and
+- the ordered use chain through which it entered the entry scene.
+
+That provenance is diagnostic and source-preservation data. It is not an
+authored transform, layout property, or globally stable node identifier in
+Versions 1 through 3. Version 4 adds explicit IDs and derives its durable
+address from those authored facts rather than promoting spans or traversal
+positions.
+
+### Validation closures
+
+Three closures remain distinct:
+
+1. **Source validation** checks the complete syntax and local invariants of
+ every source unit loaded for the operation.
+2. **Link validation** starts from the requested scene root or explicitly
+ requested component exports and traverses every syntactically contained
+ `use`, including uses under hidden nodes or with `hidden="true"`. It resolves
+ references, validates placement, and rejects cycles. An entry render need
+ not resolve an unused export; a complete library-validation operation uses
+ every exported component in that library as a root.
+3. **Render-resource preflight** inspects the materialized scene for the
+ requested render environment and applies the base Grida XML image-resource
+ contract. The module layer changes lexical origin and collision behavior;
+ it does not weaken which authored image paints a strict materializer must
+ resolve.
+
+These distinctions prevent hidden recursion from escaping link checks while
+keeping module traversal separate from image-resource resolution.
+
+## Resource provenance
+
+Relative resources retain lexical origin across component expansion.
+
+```text
+components/button.grida.xml owns
+scenes/dashboard.grida.xml owns
+```
+
+The image resolves relative to `components/button.grida.xml`, not the
+dashboard, not the current working directory, and not the location of a use.
+
+This rule is required even when two source units author the same string:
+
+```text
+components/button.grida.xml → ./texture.png
+cards/card.grida.xml → ./texture.png
+```
+
+Those references may name different resources. A materializer must therefore
+retain the pair `(source origin, authored resource identifier)` until resource
+resolution, or lower it to an equivalent collision-free runtime key while
+preserving the authored string separately. Keying both resources by the raw
+string alone is non-conforming.
+
+All image resources in the render-resource closure must be resolved or
+reported before a strict render claims success. Linking a component must not
+cause resource I/O during the painter's execution.
+
+Version 2 property arguments retain the origin of their supplying literal,
+while definition-authored defaults retain the definition's origin, as defined
+by the [component-parameter RFD](./grida-xml-component-parameters). Version 3
+assignment roots retain their caller source origin as defined by the
+[component-slot RFD](./grida-xml-component-slots). Substitution and projection
+must never make a relative resource silently change bases.
+
+## Cycles and finite expansion
+
+The semantic dependency graph is the component-expansion graph. Its vertices
+are qualified component identities, and an edge exists when one component's
+container subtree syntactically contains a use of another component in the
+link-validation closure. Hidden state does not remove this edge.
+
+Direct and indirect cycles are invalid:
+
+```text
+a.grida.xml#button
+ → b.grida.xml#icon
+ → a.grida.xml#button
+```
+
+A diagnostic reports the complete qualified chain and the use location for
+each edge. A mere source-reference loop that creates no component-expansion
+cycle need not be rejected, although a host may still limit the number of
+loaded source units.
+
+Acyclic graphs can still expand rapidly when definitions contain many uses.
+A processing environment may impose declared limits on source count, link
+depth, materialized nodes, text bytes, image bytes, or total expansion work.
+Exceeding a limit is a typed resource-limit failure. It must not produce a
+silently truncated scene.
+
+## Identity and editing
+
+The proposal distinguishes four identities:
+
+| Identity | Scope and purpose |
+| ---------------------------- | --------------------------------------------------------- |
+| Component ID | Unique export symbol inside one source unit |
+| Qualified component identity | Canonical source identity plus component ID |
+| Use occurrence | One authored instance position in a caller's source tree |
+| Materialized runtime handle | Temporary identity used by one concrete scene realization |
+
+Repeated uses of one component are distinct instances. Their materialized
+descendants must not collide merely because they share definition content.
+
+Version 1 adds only the component-export ID defined above; it still has no
+durable render-node or use-occurrence identity. This proposal therefore does
+not pretend a `name`, child index, XML path, or runtime handle can support
+stable deep overrides. If durable IDs are later admitted inside definitions, a
+descendant instance identity can be formed from an instance path plus a
+component-local durable ID. It must not be created by concatenating strings
+that can collide after nesting or renaming.
+
+A component-aware editor must retain the observable use boundary and may
+choose how instance selection, definition editing, or descendant isolation are
+presented. Editing the definition changes every linked use; editing the use
+changes only its instance-boundary intent. Detaching or flattening an instance
+is an explicit conversion that severs the reference.
+
+## Source preservation and conversion
+
+Three writer domains are distinct:
+
+1. **Source-preserving same-location write-back** operates on parsed source
+ units and may follow only an entry-relative link. It preserves each unit's
+ declared version, component-definition order, authored references,
+ argument order, resource strings, canonical base association, and
+ unresolved uses outside the requested link closure. It performs no
+ normalization that requires an unresolved target interface.
+2. **Canonical source-unit writing** is defined only when every target
+ interface needed for target-dependent normalization in that unit has
+ linked successfully. It applies the canonical rules belonging to that
+ unit's declared version.
+3. **Canonical multi-file writing** requires the complete library-validation
+ closure: every scene root, export, and syntactically contained use in the
+ program being written must link and validate. It canonicalizes each source
+ unit independently under that unit's declared version.
+
+Every writer writes source units, component definitions, and uses rather than
+the materialized clone tree. A partial-link write may be source-preserving,
+but it must not claim complete canonicalization. Failure to obtain a target
+interface required for canonical ordering is a canonical-write failure; it is
+not permission to drop the use, guess its interface, or rewrite it under
+another version.
+
+Writing the program to a different source/base mapping is relocation, not
+ordinary canonical write-back. A relocation operation must either rebase every
+affected component and resource reference while preserving its target or keep
+the original base mapping available. It must report any reference it cannot
+preserve. Full library validation remains a separate operation that starts
+from every export and therefore resolves references that an entry-only link
+may leave untouched.
+
+Flattening a linked program into a single Draft 0 scene or into a target that
+has no component/instance model is a conversion with declared loss:
+
+- component definitions become copied descendants;
+- uses cease to be linked;
+- definition edits no longer propagate;
+- instance and definition provenance may be discarded; and
+- source-relative resources must be rebased or packaged without changing the
+ referenced bytes.
+
+A converter must either complete that flattening correctly and report that
+reuse intent was lost, or reject the target. It must not emit an archive that
+appears to preserve live instances when it stores only copies.
+
+## Validation and diagnostics
+
+Failures are separated by phase:
+
+| Phase | Representative failures |
+| ------------------- | ------------------------------------------------------------------------- |
+| Source parsing | malformed XML, duplicate component ID, invalid top-level order |
+| Source resolution | missing file, disallowed scheme, unreadable source, unsupported version |
+| Linking | missing component fragment, unknown component, expansion cycle |
+| Materialization | illegal use placement, invalid component/use relationship, limits |
+| Resource resolution | missing or undecodable image with its lexical source origin |
+| Scene resolution | ordinary layout, text, path, or numeric failure with component provenance |
+
+Useful diagnostics include:
+
+- `component reference requires an ID fragment; use ./button.grida.xml#button`;
+- `component "button" is not defined in ./controls.grida.xml; available: icon-button, menu-button`;
+- `duplicate component "button" in the same source unit`;
+- ` requires id="…" using lowercase kebab-case`;
+- ` cannot declare x; place the instance with x on `;
+- `x="span 0 0" is invalid on ; component definitions own authored size`;
+- `unknown attribute width on ; component sizing is not an implicit root override`;
+- `static Version 1 cannot contain children; arg requires Version 2 and render assignments require Version 3`;
+- `unknown attribute label on ; Version 1 components do not define dynamic attributes`;
+- `component cycle: a.grida.xml#button → b.grida.xml#icon → a.grida.xml#button`;
+- `image "./texture.png" authored in components/button.grida.xml resolved to … and could not be decoded`.
+
+A cross-file diagnostic should identify the immediate use, the referenced
+definition when relevant, and the transitive reference chain. A failure in a
+materialized descendant should not be reported only with a transient runtime
+handle.
+
+## Current model compatibility
+
+The current canonical scene and packed archive contracts do not define live
+component or instance values. Their scene shape is an ordinary single-parent
+tree. The proposal does not conceal that incompatibility.
+
+Initial layout and raster rendering can conform by retaining the linked source
+program and materializing each use into an ordinary container subtree before
+scene resolution. This does not require components to become a new geometry,
+paint, or layout primitive. Component-aware querying and editing additionally
+require the retained atomic use/provenance boundary and cannot be reconstructed
+from the ordinary rendering tree alone.
+
+The ordinary materialized scene alone is insufficient for:
+
+- canonical multi-file source writing;
+- linked definition editing;
+- durable instance or descendant identity;
+- deep overrides;
+- preserving instances through the packed archive; or
+- attributing relative resources and diagnostics after source provenance is
+ discarded.
+
+Those capabilities require the retained source program described here or a
+later explicit definition/instance model in the canonical scene and archive.
+An unsupported target must flatten explicitly or reject; it must not store
+component syntax in an XML-only side table while claiming full scene-model
+round-trip.
+
+## Later exact component versions
+
+### Version 2 scalar parameterization
+
+Static reuse is intentionally the complete Version 1 language boundary.
+Strict exact-version parsing means parameterization cannot be added silently
+to `version="1"`.
+
+The [Grida XML component parameters](./grida-xml-component-parameters) RFD
+proposes the Version 2 delta: leading typed `prop` declarations, explicit
+`arg` children, exact scalar bindings, literal defaults, forwarding, lexical
+resource origins, and source-preserving materialization. Version 1 continues
+to reject all of that syntax.
+
+That later RFD also defines the one backward-link exception: a Version 2
+operation accepts a Version 1 component as an empty-interface static
+definition. It does not permit a Version 1 operation to link a Version 2
+source, so this RFD's exact-Version-1 rule remains unchanged for Version 1
+operations.
+
+### Version 3 named slot projection
+
+The [Grida XML component slots](./grida-xml-component-slots) RFD proposes the
+Version 3 delta: empty inline `` declarations and direct render
+roots under `use` carrying the contextual `slot="…"` assignment relationship.
+Zero roots erase the marker; one or more roots splice directly in caller order.
+Version 3 defines no wrapper, unnamed/default slot, fallback, requiredness, or
+explicit-empty form. Version 1 and Version 2 continue to reject that syntax.
+
+Arbitrary deep patches remain deferred until durable component-local identity
+and stale-target behavior exist. Dynamic `href`, element-name substitution,
+loops, conditions, and a general expression language remain outside static
+parameterization and projection.
+
+## Considered alternatives
+
+### Direct `href` on `use` — accepted
+
+`` is self-contained, familiar from SVG,
+copy/pasteable with its dependency visible, and uses one spelling for local
+and external references. Switching a component is one surgical attribute
+edit.
+
+### Import aliases — deferred
+
+```xml
+
+
+```
+
+Aliases shorten heavily repeated paths, but add an alias namespace, an import
+declaration that must move with copied content, and a second layer to diagnose.
+Supporting both alias and direct forms would create two canonical references.
+Aliases should be reconsidered only if real authored files demonstrate that
+reference repetition is materially harmful.
+
+### One unnamed/default component per file — rejected for the first version
+
+An implicit default makes a one-component file short, but it conflicts with an
+optional scene root, cannot be referenced locally by ID, and becomes
+ambiguous when a second component is added. Requiring a fragment keeps every
+reference explicit and stable.
+
+### Custom element names — rejected
+
+```xml
+
+
+```
+
+Custom tags make unknown built-in syntax indistinguishable from a missing
+component binding, introduce namespace and case rules, and make a component's
+source location invisible at the use site. The fixed `use` element produces
+better diagnostics and more predictable LLM output.
+
+### Textual include — rejected
+
+An include copies bytes or nodes but does not define component identity,
+instance ownership, editing propagation, cycle behavior, or caller-versus-
+definition resource provenance. It solves file concatenation, not reuse.
+
+### Engine-native shared subgraphs as the source model — deferred
+
+Sharing one definition subtree internally can be an optimization, but the
+observable scene remains equivalent to independent instances. Requiring a
+shared render graph in the source contract would force a scene-model and
+archive change before basic multi-file rendering needs one.
+
+### Arbitrary render-root definitions — rejected for Version 1
+
+Wrapping any one path, text, line, group, lens, or container as a component is
+compact, but makes caller-layout behavior depend on a hidden root kind. Span
+bindings, flex growth, cross-axis stretch, and derived-box geometry do not
+share one applicability contract across those kinds. Version 1 gives every
+component an explicit container box instead; primitive content remains a
+child of that box.
+
+### Arbitrary component-property overrides on `use` — rejected from the core
+
+Letting every component property appear on `use` couples callers to definition
+internals and creates replacement-versus-composition questions for paint,
+opacity, transforms, clipping, and layout. The core keeps only the caller
+relationship on `use`; the [component-parameter
+RFD](./grida-xml-component-parameters) proposes intentional customization
+through declared props and explicit arguments in Version 2.
+
+## Proposed conformance requirements
+
+A conforming implementation of this proposal:
+
+1. **MUST** preserve every loaded source unit, component definition, use,
+ authored reference, and lexical resource origin in its source program.
+2. **MUST** require explicit component-ID fragments and reject missing or
+ duplicate component IDs.
+3. **MUST** resolve relative component references against the containing
+ source unit through an explicit source environment whose link snapshot
+ supplies stable bytes, canonical identity, and canonical base.
+4. **MUST** reject direct or indirect component-expansion cycles before scene
+ resolution, including cycles through hidden uses.
+5. **MUST** require every linked Version 1 component source to declare exactly
+ `version="1"` unless a later specification defines cross-version linking.
+6. **MUST** make each use observably equivalent to an independent ordinary
+ container subtree at the use's one caller position.
+7. **MUST** preserve ordinary layout, painter order, local coordinates,
+ fills, strokes, text, and resource semantics inside the component.
+8. **MUST** distinguish a source/link failure from an ordinary scene or
+ resource-resolution failure and report cross-file provenance.
+9. **MUST NOT** resolve relative images against the entry source merely
+ because the materialized scene is flat.
+10. **MUST NOT** serialize materialized copies as canonical component/use
+ source.
+11. **MUST** preserve each source unit's declared version and every unresolved
+ use outside the available link closure during source-preserving
+ same-location write-back.
+12. **MUST NOT** claim canonical multi-file output without completing the full
+ library-validation closure required by target-dependent normalization.
+13. **MUST NOT** claim live-instance archive round-trip when the target stores
+ only flattened nodes.
+14. **MUST** retain each use as an observable component/provenance boundary,
+ even when rendering consumes an expanded tree; interaction policy remains
+ a consumer concern.
+15. **MAY** cache, intern, or share immutable definition data when that does
+ not change instance behavior or diagnostics.
+16. **MAY** expose declared processing limits, but exceeding them must fail
+ explicitly rather than truncate the scene.
diff --git a/docs/wg/format/grida-xml-properties.md b/docs/wg/format/grida-xml-properties.md
new file mode 100644
index 0000000000..f4a8baf2d9
--- /dev/null
+++ b/docs/wg/format/grida-xml-properties.md
@@ -0,0 +1,714 @@
+---
+title: "Grida XML property registry"
+description: "Cross-draft inventory of Grida XML elements, attributes, applicability, and unresolved property syntax, grounded in the production scene model."
+keywords:
+ - grida xml
+ - property registry
+ - scene language
+ - xml attributes
+ - vector graphics
+ - layout
+tags:
+ - internal
+ - wg
+ - format-schema
+ - canvas
+ - authoring
+ - layout
+ - text
+format: md
+---
+
+# Grida XML property registry
+
+**Status:** Design inventory; companion to the [Grida XML Draft 0
+RFD](./grida-xml).
+
+Later-version reuse vocabulary is specified by the [modules and static
+component reuse](./grida-xml-modules), [component
+parameters](./grida-xml-component-parameters), and [component
+slots](./grida-xml-component-slots) RFDs. Those rows remain **Design** until
+their language versions are accepted; Draft 0 continues to reject them.
+
+The [durable-addressing RFD](./grida-xml-addressing) accepts a Version 4
+identity and typed effective-value boundary. Version 4 inherits the earlier
+component vocabulary, but it does not change which rows Draft 0 accepts and
+does not make a registered property animatable.
+
+This page is the canonical inventory of XML-facing property names and their
+valid targets. It is not, by itself, a grammar extension. Only rows marked
+**Draft 0** are accepted by a Draft 0 reader. **Placeholder** and **Design**
+rows remain invalid until a later, versioned Grida XML specification defines
+their complete syntax and semantics.
+
+Before this registry, Grida had two related documents but no complete XML
+applicability crosswalk:
+
+- [Grida IR](./grida) inventories the production scene model.
+- [Grida XML](./grida-xml) defines the small normative Draft 0 authored
+ language.
+
+This registry connects those views without making the XML spelling mirror
+implementation field names.
+
+## Grounding and precedence
+
+This inventory was reconciled against the canonical scene model and the packed
+archive contract. The scene model is the source of truth for representable
+scene semantics; archive coverage separately determines what `.grida` can
+persist. Differences between those contracts are recorded below rather than
+being averaged into a third model.
+
+Draft 0's repeatable stroke geometry is the one accepted extension beyond that
+current production model. It reuses the existing stroke geometry plus `Paints`
+pair but requires the production engine and archive to own an ordered list of
+those pairs; the seam is recorded explicitly below.
+
+For paths, the existing contracts do not present one uniform model: one form
+stores raw coordinates and derives intrinsic geometry, while another defines a
+canonical shape mapped into a layout box. Draft 0 selects the latter and fixes
+its reference rectangle to `0 0 1 1`. This is an explicit compatibility choice,
+not an attempt to average the two forms or hide a raw-path conversion behind
+XML-only state.
+
+Grida XML projects that model as an authored language:
+
+1. XML uses familiar, semantic names when CSS, SVG, HTML, or Flutter already
+ provide one.
+2. When those vocabularies have no equivalent, an established design, motion,
+ or photo-tool term is preferable to a Grida-only invention.
+3. Multi-word names use kebab-case.
+4. Element names carry the primary type. The bounded `gradient` family alone
+ uses `kind` to select one of its structurally shared production variants; a
+ generic paint or render-node `kind` does not exist.
+5. Tree nesting carries parenthood and painter order; `parent`, `children`,
+ and archive ordering fields are not authored properties.
+6. Resolved bounds, glyph runs, vector materializations, caches, and transient
+ runtime identifiers are derived data, not XML properties.
+7. One meaning gets one canonical writer spelling. A compact form may own a
+ deliberately disjoint canonical domain, as `fill="#RRGGBB"` does for one
+ ordinary solid; it never combines with the expanded channel.
+
+### Status key
+
+| Status | Meaning |
+| --------------- | --------------------------------------------------------------------------- |
+| **Draft 0** | Normative syntax accepted by the current RFD |
+| **Version 4** | Accepted only by the exact Version 4 source contract |
+| **Placeholder** | Production concept exists; proposed XML name and target are held for design |
+| **Design** | Selected or unresolved later-version design; not operative Draft 0 syntax |
+| **Derived** | Runtime, archive, or resolved data that should not be authored directly |
+
+Placeholder rows are deliberately non-operative. A Draft 0 reader must still
+diagnose them as unknown syntax.
+
+## Element inventory
+
+The table covers the production node set as well as XML-only source constructs.
+“Children” means render-node children, not typed property children such as
+`fill`, `stroke`, or gradient `stop` elements. “Assigned” means Version 3
+direct render roots projected to a component declaration rather than ordinary
+children owned by `use`. `tspan` and `slot` are listed explicitly as contextual
+source constructs even though neither is a scene node.
+
+| XML element | Production concept | Children | Status | Source and use |
+| ----------------- | ------------------------------------- | --------- | --------------- | --------------------------------------------------------------------------- |
+| `grida` | Document envelope | One root | **Draft 0** | XML; carries only `version` |
+| `component` | Boxed source definition | Yes | **Design** | Top-level, non-painting definition with `container` semantics |
+| `use` | Source component instance | Assigned | **Design** | Versioned component reference; Version 3 may carry direct assignment roots |
+| `prop` | Component scalar declaration | No | **Design** | Leading contextual child of a Version 2 `component`; never renders |
+| `arg` | Component scalar argument | No | **Design** | Contextual property child of a Version 2 `use`; never a render child |
+| `slot` | Component insertion marker | No | **Design** | Empty named Version 3 declaration at a render-child position; never renders |
+| `container` | Container | Yes | **Draft 0** | Flutter/HTML; boxed composition and layout owner |
+| `group` | Group | Yes | **Draft 0** | SVG `g`; derived bounds, no own geometry |
+| `rect` | Rectangle | Yes | **Draft 0** | SVG; direct primitive |
+| `ellipse` | Ellipse, ring, or arc | Yes | **Draft 0** | SVG base primitive; arc parameters are placeholders |
+| `line` | Line | Yes | **Draft 0** | SVG; stroke-only primitive with repeatable stroke geometries |
+| `text` | Text or attributed-text node | No | **Draft 0** | Owns one text box, paragraph/default style, and node paints |
+| `tspan` | `StyledTextRun` | No | **Draft 0** | Flat direct child of `text`; contextual run, never a scene node |
+| `lens` | Source transform wrapper | Yes | **Draft 0** | Grida source construct; resolves through group/transform semantics |
+| `image` | Image node outside paint channels | No | **Placeholder** | HTML/SVG; contextual with the existing image-paint element |
+| `path` | Box-mapped unit-space path | Yes | **Draft 0** | Full SVG path-data grammar in the fixed `0 0 1 1` reference rectangle |
+| `polygon` | Arbitrary closed polygon | Yes | **Placeholder** | SVG; explicit coordinate list |
+| `regular-polygon` | Parametric regular polygon | Yes | **Placeholder** | Explicit name; point-count grammar still needs validation rules |
+| `star` | Parametric regular star polygon | Yes | **Placeholder** | Familiar design-tool name; inner radius and point count are parametric |
+| `vector` | Editable vector network | Yes | **Design** | No established XML vocabulary for vertices, handles, and filled regions |
+| `boolean` | Boolean path operation | Yes | **Design** | Child ownership and live-versus-flattened behavior must be fixed |
+| `markdown` | Opaque Markdown embed | No | **Design** | Runtime feature; source escaping, resources, and security need a contract |
+| `html` | Opaque HTML/CSS embed | No | **Design** | Runtime feature; embedding HTML inside XML needs an explicit boundary |
+| `tray` | Canvas/editor organizational tray | Yes | **Design** | Production node, but not yet justified as a portable authored primitive |
+| `shape` | Future custom-shape definition or use | Undecided | **Design** | Reserved by the Draft 0 RFD; no current rendering semantics |
+| _none_ | Initial viewport container | Root | **Derived** | The `grida` envelope and render root materialize this host boundary |
+| _none_ | Error/import placeholder | No | **Derived** | Diagnostic result, never canonical authored content |
+
+Attributed text remains one `` node with direct, flat `` children.
+`tspan` contains exact character data plus an optional literal-first `fill`
+property; it has no scene identity, box, transform, opacity, layout, or render
+children. `` is not an alias and a reader must never support both
+spellings.
+
+## Draft 0 attribute registry
+
+This is the compact applicability index for syntax already defined by the
+[Draft 0 RFD](./grida-xml). The RFD remains normative for value grammar,
+defaults, contradictions, and resolution behavior.
+
+| Attribute | Valid on | Inspiration | Purpose |
+| -------------------------- | ------------------------------------------------------------------ | -------------------- | ---------------------------------------------------------- |
+| `version` | `grida` | XML | Selects the exact language version; currently exactly `0` |
+| `name` | All Draft 0 render elements | HTML/SVG | Human-readable label; not durable identity |
+| `x`, `y` | Draft 0 render elements; omitted for in-flow flex children | SVG/CSS | Start/end/center/span binding; root target is the viewport |
+| `width` | `container`, `rect`, `ellipse`, `line`, `path`, `text` | CSS/SVG | Authored width or element-specific `auto` |
+| `height` | `container`, `rect`, `ellipse`, `path`, `text`; never `line` | CSS/SVG | Authored height or element-specific `auto` |
+| `min-width`, `max-width` | `container`, `rect`, `ellipse`, `line`, `path`, `text` | CSS | Width constraints |
+| `min-height`, `max-height` | `container`, `rect`, `ellipse`, `path`, `text`; never `line` | CSS | Height constraints |
+| `aspect-ratio` | `rect`, `ellipse`, `path` | CSS | Supplies one otherwise unresolved box axis |
+| `corner-radius` | `container`, `rect` | CSS/Flutter | One or four circular or elliptical corner radii |
+| `corner-smoothing` | `container`, `rect` | Design tools | Continuous smoothing of circular rounded corners |
+| `rotation` | All Draft 0 render elements | Flutter/design tools | Clockwise visual rotation in degrees |
+| `flip-x`, `flip-y` | All Draft 0 render elements | Design tools | Native visual reflection |
+| `opacity` | All Draft 0 render elements | CSS/SVG | Composites the node and descendants |
+| `hidden` | All Draft 0 render elements | HTML | Removes the subtree from layout and painting |
+| `fill` | `container`, `rect`, `ellipse`, `path`, `text`; contextual `tspan` | SVG | One-solid node fill or explicit run-fill override |
+| `d` | `path` | SVG | Complete path-data grammar in the fixed unit rectangle |
+| `fill-rule` | `path` | SVG/CSS | `nonzero` or `evenodd`; defaults to `nonzero` |
+| `clips` | `container` | Flutter/design tools | Clips descendant content to the container shape |
+| `font-size` | `text`, `tspan` | CSS/SVG | Positive finite logical-pixel default or run override |
+| `font-weight` | `text`, `tspan` | CSS | Integer weight from 1 through 1000 |
+| `font-style` | `text`, `tspan` | CSS | `normal` or `italic` |
+| `ops` | `lens` | CSS/SVG | Ordered 2D transform functions |
+| `layout` | `container` | CSS | Selects `none` or `flex` |
+| `direction` | `container` with `layout="flex"` | CSS | Main-axis direction |
+| `wrap` | `container` with `layout="flex"` | CSS | Enables or disables wrapping |
+| `gap` | `container` with `layout="flex"` | CSS | Main/cross spacing |
+| `padding` | `container` | CSS/Flutter | Insets the content box |
+| `main` | `container` with `layout="flex"` | Flutter | Main-axis distribution |
+| `cross` | `container` with `layout="flex"` | Flutter | Cross-axis alignment |
+| `flow` | Child of a flex container | CSS | Selects in-flow or absolute participation |
+| `grow` | In-flow child of a flex container | CSS/Flutter | Main-axis growth factor |
+| `align` | In-flow child of a flex container | CSS/Flutter | Per-child cross-axis override |
+
+`font-size`, `font-weight`, and `font-style` default on `text` to `16`, `400`,
+and `normal`. The same attributes on `tspan` override the owning `text`
+default for that run; omission inherits from `text`, never a preceding run.
+`size` and a generic `style` attribute are not Grida XML spellings.
+
+The [rounded box geometry contract](./grida-xml#rounded-box-geometry) defines
+the exact grammar. In registry form, `corner-radius` is one or exactly four
+non-negative finite horizontal radii, optionally followed by `/` and an
+independent one-or-four-value vertical list. Four-value order is top-left,
+top-right, bottom-right, bottom-left. `corner-smoothing` is finite in `[0, 1]`.
+Both attributes default to zero and apply only to `container` and `rect`.
+
+The canonical writer shortens an all-equal axis list to one value, otherwise
+emits all four; it omits the slash when every horizontal and vertical radius
+pair is equal. It omits zero defaults, but preserves nonzero smoothing with
+zero radii as dormant intent. Oversized ordinary radii use one edge-sum-based
+proportional factor; nonzero smoothing uses the production profile's
+per-corner half-short-side cap. Neither resolved form is rewritten in source.
+
+For `path`, `d` is required and uses the complete SVG path-data grammar in a
+fixed `0 0 1 1` reference rectangle. Validation is against exact tight curve
+geometry, which must stay within the closed unit box. The geometry maps
+nonuniformly to the resolved declared box before fill and stroke construction;
+it is never measured or tight-fitted. `fill-rule` defaults to `nonzero` and
+also accepts `evenodd`. Paints use the full resolved box, children remain
+box-local and unclipped, and the default fill is black. Path strokes accept
+only scalar width; center is the default and is required whenever any contour
+is open. Resolution maps the command stream once; bounds and all paint channels
+consume that exact mapped geometry. Numeric unrepresentability is a resolution
+failure, never permission to clamp, substitute the declared box, or retain
+partial path ink.
+
+## Proposed later-version module vocabulary
+
+The [module and component RFD](./grida-xml-modules) proposes this source-only
+vocabulary. These rows are not operative Draft 0 grammar.
+
+| Element or attribute | Valid on | Proposed contract |
+| ----------------------- | ----------------------- | ------------------------------------------------------------------------------- |
+| `component` | Direct child of `grida` | Non-painting boxed definition with the ordinary `container` grammar |
+| `id` | `component` | Unique lowercase kebab-case export symbol in that source unit |
+| `use` | Render-child position | One source instance materialized as the referenced ordinary container subtree |
+| `href` | `use` | Required local or source-relative reference with a component-ID fragment |
+| `name` | `use` | Optional human-readable instance label; not definition or durable node identity |
+| `x`, `y` | `use` in free placement | Non-span parent-relative binding of the component container |
+| `flow`, `grow`, `align` | `use` under flex layout | Flex participation of the component container |
+| `hidden` | `use` | Instance gate that removes that use from layout and paint when true |
+
+Component definitions precede an optional one-container scene root. A source
+may therefore be a component library, a render entry, or both. Every component
+reference requires an explicit fragment, including local references such as
+`href="#button"`; there is no default component export or implicit scene-root
+export in the proposal.
+
+`use` intentionally does not accept width, height, paints, opacity, transforms,
+or arbitrary component properties in the static Draft 1 proposal. The boxed
+definition owns those values. The [component-parameter
+RFD](./grida-xml-component-parameters) proposes intentional scalar
+customization through `prop`/`arg` in Version 2; Version 1 continues to reject
+them.
+
+### Proposed Version 2 scalar parameter vocabulary
+
+These rows summarize the [component-parameter
+RFD](./grida-xml-component-parameters). They remain non-operative design
+syntax until accepted and implemented.
+
+| Element or form | Valid on or in | Proposed contract |
+| -------------------- | --------------------------------------------- | ------------------------------------------------------------------- |
+| `prop` | Leading direct child of Version 2 `component` | Empty typed scalar declaration; no layout or painter position |
+| `name` | `prop`, `arg` | Lowercase kebab-case component-local API member name |
+| `type` | `prop` | Required closed scalar category |
+| `default` | `prop` | Optional definition-owned literal; omission makes the prop required |
+| `values` | `prop type="enum"` | Required non-empty set of accepted enum tokens |
+| `arg` | Direct child of Version 2 `use` | Empty explicit argument; still not a render child |
+| `value` | `arg` | Required literal or exact outer-prop forwarding binding |
+| `{prop-name}` | Compatible established scalar value position | Complete-value binding; attributes do not support interpolation |
+| Text binding segment | Direct `text` or `tspan` character content | Inserts one string value as characters without reparsing |
+
+Version 2 defines no universal null, expression language, or parameterized
+attribute presence. A use may contain `arg` property children but still may
+not contain render children. Version 3 projection is specified separately and
+does not backport slot declarations, assignment attributes, or render children
+into Version 2.
+
+### Proposed Version 3 named slot vocabulary
+
+These rows summarize the [component-slot RFD](./grida-xml-component-slots).
+They remain non-operative design syntax until accepted as a stable language
+version.
+
+| Element or form | Valid on or in | Proposed contract |
+| ------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
+| `` | Render-child position in a Version 3 component | Empty named projection marker; no layout or painter position |
+| `name` | `` | Required unique component-local lowercase kebab-case slot name |
+| Assignment root | Direct render child of Version 3 `use` after `arg*` | Caller-authored ordinary root projected at the named marker |
+| `slot` relationship | Direct assignment root only | Required declared target name; consumed by projection and never materialized |
+
+The `slot` assignment relationship is contextual source metadata, not an
+ordinary render property. It is invalid on a node outside the direct
+assignment position, is absent from the Draft 0 attribute registry, and never
+appears in the ordinary materialized scene. Version 3 has no wrapper,
+unnamed/default slot, fallback, requiredness, or explicit-empty form. Versions
+0, 1, and 2 continue to reject all slot syntax.
+
+### Version 4 durable identity vocabulary
+
+These rows summarize the accepted [durable-addressing
+RFD](./grida-xml-addressing). Version 4 inherits the complete Version 3
+language but links only Version 4 component sources so every materialized
+authored node has a complete address.
+
+| Attribute or identity | Valid on or in | Version 4 contract |
+| --------------------- | ------------------------------------ | -------------------------------------------------------------------------- |
+| `id` | Every authored render element | Required literal lowercase-kebab member ID, unique in its lexical owner |
+| `id` | Every `use` | Required caller-owned occurrence ID in the same owner-local namespace |
+| `id` | `component` | Existing source-unit export ID; qualifies the owner containing `Root` |
+| `root` | Abstract component member addressing | Distinguished identity only; not an authored attribute |
+| `name` | Render elements and `use` | Optional human label; never an identity fallback |
+| occurrence path | Materialized component descendants | Ordered outer-to-inner sequence of caller-owner plus authored-use-ID pairs |
+
+Versions 0 through 3 do not accept this new render/use `id` syntax and never
+receive generated durable addresses.
+
+### Version 4 effective-value registry
+
+Version 4 also fixes the node-level semantic properties that may enter one
+immutable effective-value evaluation. This is not new authored animation
+syntax and registration does not mean a property is animatable. A missing
+entry reads the materialized base value; optional states live inside the exact
+value type rather than a universal `null`.
+
+Keys name model properties, not alternative source spellings. Compact and
+expanded fills both project `fills`; source `hidden` projects the inverse
+`active` value; the flex attributes collectively project one `layout` value.
+
+Impact legend: M = measure, L = layout, T = transform, B = bounds, P = paint,
+and R = resource.
+
+| Semantic key | Exact value type | Valid target | Base projection | Impact |
+| -------------------------- | --------------------- | ------------------------------------------------------------ | --------------------------------------------- | ----------- |
+| `x`, `y` | `AxisBinding` | Every node | Materialized axis binding | M/L/T/B/P |
+| `width` | `SizeIntent` | `container`, `rect`, `ellipse`, `line`, `path`, `text` | Fixed or auto width intent | M/L/T/B/P |
+| `height` | `SizeIntent` | `container`, `rect`, `ellipse`, `path`, `text`; never `line` | Fixed or auto height intent | M/L/T/B/P |
+| `min-width`, `max-width` | `OptionalNumber` | `container`, `rect`, `ellipse`, `line`, `path`, `text` | Optional width constraint | M/L/T/B/P |
+| `min-height`, `max-height` | `OptionalNumber` | `container`, `rect`, `ellipse`, `path`, `text`; never `line` | Optional height constraint | M/L/T/B/P |
+| `aspect-ratio` | `OptionalAspectRatio` | `rect`, `ellipse`, `path` | Optional width/height ratio | M/L/T/B/P |
+| `active` | `Boolean` | Every node | Positive activity; inverse of `hidden` | M/L/T/B/P/R |
+| `rotation` | `Number` | Every node | Clockwise degrees | M/L/T/B/P |
+| `flip-x`, `flip-y` | `Boolean` | Every node | Native mirror flags | M/L/T/B/P |
+| `flow` | `Flow` | Every node | In-flow or absolute participation | M/L/T/B/P |
+| `grow` | `Number` | Every node | Main-axis growth factor | M/L/T/B/P |
+| `self-align` | `SelfAlign` | Every node | Per-child alignment from `align` | M/L/T/B/P |
+| `opacity` | `Number` | Every node | Node/subtree opacity | P |
+| `layout` | `Layout` | `container`/frame | Complete normalized layout behavior | M/L/T/B/P |
+| `clips-content` | `Boolean` | `container`/frame | Content-clip flag from `clips` | B/P |
+| `corner-radius` | `CornerRadius` | `container`/frame and `rect` | Four-corner elliptical radii | B/P |
+| `corner-smoothing` | `Number` | `container`/frame and `rect` | Smoothing factor | B/P |
+| `fills` | `Paints` | Fill-paintable frame, rect, ellipse, path, and text | Complete ordered fills | P/R |
+| `strokes` | `Strokes` | Stroke-paintable frame, shape, and text | Complete ordered stroke geometries and paints | B/P/R |
+
+Rotation is any finite number. Growth is finite and non-negative. Opacity and
+corner smoothing are finite in `[0, 1]`. Fixed sizes and present constraints
+are finite and non-negative; a present aspect ratio contains two finite
+positive numbers. Aggregate layout, corner, fill, and stroke values retain all
+of their static nested and renderer-capability validation. Cross-property
+rules are checked against the complete effective node state, so an otherwise
+valid replacement cannot combine with an authored value to create a state the
+renderer would silently omit. Keys with R impact may select logical resources
+but never perform loading; resource readiness and bytes belong to a separately
+revisioned evaluation environment. The exact addressing,
+applicability, nullability, validation, and static-equivalence laws are
+normative in [Grida XML durable addressing](./grida-xml-addressing).
+
+## Scene-backed property placeholders
+
+These tables reserve discussion slots for properties in the canonical scene
+model. They do not claim the proposed XML spelling is final.
+
+### Identity, compositing, transform, and box geometry
+
+| Candidate XML property | Valid on | Production concept | Inspiration | Status |
+| ---------------------- | ---------------------------------------- | ---------------------------------------- | ------------ | --------------- |
+| `id` | Every authored render node | Durable owner-local member identity | HTML/SVG | **Version 4** |
+| `locked` | Every authored render node, if persisted | Editor lock metadata | Design tools | **Design** |
+| `blend-mode` | Every compositing render node | Layer blend mode, including pass-through | CSS | **Placeholder** |
+| `mask-type` | A node that acts as a mask source | Geometry, alpha, or luminance mask | CSS/SVG | **Design** |
+| `transform` | Render nodes | Post-layout 2D affine transform | CSS/SVG | **Design** |
+| `transform-origin` | Boxed render nodes | Post-layout transform pivot | CSS | **Design** |
+
+Version 4 `id` maps to durable authored identity, not the runtime's transient
+node handle. It is required, literal, lowercase-kebab, and unique within its
+scene or component owner. The same Version 4 spelling on `use` identifies the
+caller-owned occurrence; the component export `id` remains a separate
+module-local symbol. Component roots use the distinguished root-member
+identity rather than a second attribute. The exact address and occurrence-path
+contract is defined by [Grida XML durable addressing](./grida-xml-addressing).
+
+`transform` also cannot be admitted until its relationship to native
+`rotation`, `flip-x`, `flip-y`, and `lens ops` has one canonical answer.
+Versions 0 through 3 continue to reject `id` on render nodes and `use`.
+
+### Primitive geometry
+
+| Candidate XML property | Valid on | Value direction | Inspiration | Status |
+| ---------------------- | ------------------------- | ----------------------------------------------- | -------------------- | --------------- |
+| `inner-radius` | `ellipse`, `star` | Normalized inner radius; exact domains differ | Design tools | **Placeholder** |
+| `start-angle` | `ellipse` arc/ring | Clockwise degrees | Flutter/design tools | **Placeholder** |
+| `sweep-angle` | `ellipse` arc/ring | Clockwise angular extent | Flutter | **Placeholder** |
+| `points` | `polygon` | SVG coordinate-pair list | SVG | **Placeholder** |
+| `point-count` | `regular-polygon`, `star` | Integer count, at least three | Design tools | **Placeholder** |
+| `view-box` | `path` | Optional stable non-unit reference rectangle | SVG | **Design** |
+| `fill-rule` | Future vector regions | `nonzero` or `evenodd` | SVG/CSS | **Design** |
+| `operation` | `boolean` | `union`, `intersection`, `difference`, or `xor` | Design tools | **Design** |
+| `marker-start` | `line`, `path`, `vector` | Marker preset or future marker reference | SVG | **Placeholder** |
+| `marker-end` | `line`, `path`, `vector` | Marker preset or future marker reference | SVG | **Placeholder** |
+
+The runtime currently calls the ellipse extent `angle`; `sweep-angle` is the
+clearer authored candidate because it states what the number means. Draft 0
+already uses SVG's established `d` name for box-mapped path commands rather
+than exposing a storage-oriented `data` name.
+
+`view-box` remains deferred. If introduced, it would let imported coordinates
+retain a stable non-unit reference rectangle, but that rectangle must be
+authored intent with positive extents and must never be re-derived from path
+tight bounds after an edit. Its omission, canonical writer behavior, and
+interaction with the fixed unit form need one versioned rule before the
+attribute can be accepted.
+
+### Scene image
+
+| Candidate XML property | Valid on | Production concept | Inspiration | Status |
+| ----------------------- | -------------------------- | -------------------------------------------- | ---------------- | ------------------------------------------------------------ |
+| `src` | Scene `image`; image paint | Logical resource identifier | HTML/SVG | **Placeholder** for scene image; **Draft 0** for image paint |
+| `fit` | Scene `image`; image paint | `cover`, `contain`, `fill`, or intrinsic fit | Flutter/CSS | **Placeholder** for scene image; **Draft 0** for image paint |
+| `object-position` | Scene `image`; image paint | Alignment inside the paint box | CSS | **Design** |
+| `image-orientation` | Scene `image`; image paint | Quarter-turn/orientation handling | CSS | **Design** |
+| `image-transform` | Image paint | Free affine image placement | SVG/design tools | **Design** |
+| `image-repeat` | Image paint | Repeat/tile behavior | CSS | **Design** |
+| `exposure` | Image paint | Image color adjustment | Design tools | **Placeholder** |
+| `contrast` | Image paint | Image color adjustment | CSS/design tools | **Placeholder** |
+| `saturation` | Image paint | Image color adjustment | CSS/design tools | **Placeholder** |
+| `temperature`, `tint` | Image paint | Image color adjustment | Photo tools | **Placeholder** |
+| `highlights`, `shadows` | Image paint | Tonal adjustment | Photo tools | **Placeholder** |
+
+Resource declaration, packaging, network policy, intrinsic size, and missing
+resource behavior must be shared by scene images and image paints. The same
+`src` must not acquire unrelated identity semantics merely because its
+`image` appears in a different context.
+
+## Structural property elements
+
+Some production properties are ordered or structured values and should remain
+XML children rather than become mini-languages inside attributes.
+
+| Property element | Valid parent | Status | Contract direction |
+| -------------------- | ------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------- |
+| `fill` | `container`, `rect`, `ellipse`, `path`, `text`, `tspan` | **Draft 0** | Expanded ordered `Paints`; singular and literal-first when under `tspan` |
+| `stroke` | `container`, `rect`, `ellipse`, `line`, `path`, `text` | **Draft 0** | Repeatable geometry owning ordered `Paints`; empty only with non-default geometry |
+| `solid` | `fill` or `stroke` | **Draft 0** | Typed solid paint |
+| `gradient` | `fill` or `stroke` | **Draft 0** | Typed gradient family with required `kind` and `stop` children |
+| `image` | `fill` or `stroke` | **Draft 0** | Contextual image paint |
+| `stop` | `gradient` | **Draft 0** | Ordered offset/color pair |
+| `effects` | Effect-capable node | **Design** | Typed effect values; ordering and multiplicity are unresolved |
+| `tspan` | `text` | **Draft 0** | Flat contextual run with exact non-empty character data and explicit overrides |
+| `network` | `vector` | **Design** | Editable vertices, Bézier segments, loops, regions, and region fills |
+| `resources` | Document-level declaration scope | **Design** | Portable logical IDs and packaged/external resource policy |
+| `component` | Direct child of a later-version `grida` envelope | **Design** | Named, non-painting boxed definition with ordinary `container` semantics |
+| `use` | Render-child position in a later-version source | **Design** | Source-relative component-ID instance; no independent paint wrapper |
+| `prop`, `arg` | Version 2 component definition and use | **Design** | Proposed typed scalar API; complete contract lives in the parameter RFD |
+| `slot` | Render-child position inside a Version 3 component | **Design** | Empty named projection marker; complete contract lives in the component-slot RFD |
+| Animation vocabulary | Document, node, or property scope | **Design** | Requires a dedicated timing and addressing RFD |
+
+### Paint attribute registry
+
+These paint attributes are already normative in Draft 0. Their semantics are
+defined in the [paint channels
+section](./grida-xml#paint-channels-and-vocabulary).
+
+| Attribute | Valid on | Purpose |
+| ------------ | ------------------------------------------- | -------------------------------------------------- |
+| `visible` | Every typed paint | Retains an inactive paint in its ordered stack |
+| `opacity` | Every typed paint; also optional on `stop` | Paint alpha, or stop alpha in `stop` context |
+| `blend-mode` | Every typed paint | Per-paint compositing mode |
+| `color` | `solid`, `stop` | Solid or stop color |
+| `kind` | `gradient` | Required linear, radial, sweep, or diamond variant |
+| `from`, `to` | `gradient kind="linear"` | Unit-paint-space endpoints |
+| `transform` | `gradient` | Unit-paint-space affine transform |
+| `tile-mode` | `gradient kind="linear"` or `kind="radial"` | Sampling outside the ramp |
+| `offset` | `stop` | Normalized ramp position |
+| `src` | Image paint inside `fill` or `stroke` | Logical image resource identifier |
+| `fit` | Image paint inside `fill` or `stroke` | Cover, contain, fill, or intrinsic image placement |
+
+`opacity` is contextual: on a gradient or image it remains a floating-point
+paint property, while on `solid` and `stop` it materializes through color
+alpha as described by the RFD's quantization rule.
+
+A gradient `transform` must be finite and mathematically invertible. Linear
+endpoints remain valid outside the unit box, but after centered-alignment
+lowering their binary32 unit-space distance must be greater than `2^-15`;
+shorter ramps are rejected rather than silently narrowed to the backend's
+degenerate-color fallback. The same authored-state checks apply to source,
+canonical writing, and a replacement `Paints` value.
+
+This does not promise that every transform is numerically invertible after a
+particular resolved paint box is composed in a raster backend. Evaluation must
+check that exact composed matrix and construct the selected gradient
+implementation before publishing the frame. Extremely small or large values
+can be accepted here yet fail for one box and succeed for another; such a
+failure is contextual frame evaluation, not source repair or a second paint-
+validity rule.
+
+## Stroke geometry attribute registry
+
+Each `stroke` is one geometry with direct typed paint children. An empty paint
+list is valid only when at least one geometry value differs from the target's
+defaults. These scoped attributes are normative in Draft 0:
+
+| Attribute | Value | Valid targets | Purpose |
+| ------------- | ------------------------------------------ | ------------------------------------------------ | -------------------------------------------- |
+| `width` | one or exactly four non-negative numbers | every `stroke`; four only on `container`, `rect` | Uniform or top-right-bottom-left width |
+| `align` | `inside`, `center`, or `outside` | every `stroke`; open geometry must be `center` | Placement relative to the original outline |
+| `cap` | `butt`, `round`, or `square` | `line`, `path` | Open-contour endpoint shape |
+| `join` | `miter`, `round`, or `bevel` | `container`, `rect`, `path` | Joined-contour corner shape |
+| `miter-limit` | positive finite number | `container`, `rect`, `path` | Miter-to-bevel cutoff ratio |
+| `dash-array` | space-separated non-negative finite values | `container`, `rect`, `ellipse`, `line`, `path` | Repeating dash-gap lengths in logical pixels |
+
+The production model already separates one stroke `Paints` stack from one
+stroke geometry. One XML `stroke`, using only attributes supported by its
+target, projects that pair directly. A default empty pair canonicalizes to
+omission; non-default empty geometry remains explicit. Repeatable `stroke`
+elements are an accepted extension to an ordered list of those pairs; the
+current production model still needs that multiplicity before it can
+materialize more than one without loss.
+
+### Per-side width grammar
+
+One `width` number selects uniform stroke width. Exactly four numbers select
+rectangular widths in top, right, bottom, left order and are valid only for a
+`container` or `rect`. Two- and three-number expansion, commas, units, negative
+values, and non-finite values are invalid. The omitted default is uniform `1`.
+
+Four equal positive values normalize to the uniform state; four zeros and a
+scalar zero normalize to the no-width state. A canonical writer omits uniform
+`1`, uses one number for every other uniform or no-width state, and writes all
+four values only when they differ. Applicability is validated before this
+normalization, so four values remain invalid on an ellipse, line, path, or
+text even when equal.
+
+The four local-side widths produce one outer-minus-inner coverage ring with one
+shared ordered paint stack. `inside`, `center`, and `outside` use outward and
+inward fractions `(0, 1)`, `(1/2, 1/2)`, and `(1, 0)`, respectively. Each
+corner's outer radius adds the outward fraction of its adjacent horizontal and
+vertical widths; its inner radius subtracts the inward fractions and clamps
+each result to zero. A zero or negative inner-box extent makes the inner
+contour empty, saturating coverage to the outer contour without rewriting
+source widths.
+
+Ordinary circular and elliptical corner radii and continuous `dash-array`
+traversal remain valid. A zero-width side has no coverage but still advances
+the dash metric. Per-side widths currently require zero corner smoothing,
+`join="miter"`, and `miter-limit="4"`; other combinations are rejected rather
+than accepted as state the rectangular renderer cannot honor.
+
+### Deferred stroke geometry
+
+| Candidate XML property | Valid on | Production concept | Status |
+| ---------------------- | ------------------------------ | -------------------------------------------- | --------------- |
+| Width-profile grammar | Future `vector` | Ordered normalized-position/half-width stops | **Design** |
+| `dash-offset` | `stroke` | Phase offset into the dash cycle | **Design** |
+| `marker-start` | Open path-like stroke geometry | Endpoint marker preset or reference | **Placeholder** |
+| `marker-end` | Open path-like stroke geometry | Endpoint marker preset or reference | **Placeholder** |
+
+Variable-width profiles need structured grammar of their own and should not be
+forced into an opaque comma-separated attribute merely to look CSS-like.
+
+## Text attributes and property placeholders
+
+`text` owns the paragraph, default text style, and node paints. A direct,
+flat `tspan` is only a contextual run and inherits every omitted run property
+from the owning `text`, not from its preceding sibling. Draft 0 uses explicit
+kebab-case attributes and defines no `style` mini-language.
+
+This registry inventories authored XML properties only. The [Universal Shaped
+Text Layout](../feat-paragraph/text-layout) RFD owns their eventual shaping,
+font resolution, line construction, UTF-8 mapping, metrics, and resolved
+bounds; adding a property here must project into that one resolution contract,
+not create a second measurement or rendering path.
+
+| Candidate XML property | Valid on | Production concept | Inspiration | Status |
+| --------------------------- | --------------- | ----------------------------------------- | ----------- | --------------- |
+| `font-family` | `text`, `tspan` | Font family | CSS | **Placeholder** |
+| `font-size` | `text`, `tspan` | Positive finite font size | CSS/SVG | **Draft 0** |
+| `font-weight` | `text`, `tspan` | Integer weight from 1 through 1000 | CSS | **Draft 0** |
+| `font-stretch` | `text`, `tspan` | Font width | CSS | **Placeholder** |
+| `font-style` | `text`, `tspan` | `normal` or `italic` | CSS | **Draft 0** |
+| `font-kerning` | `text`, `tspan` | Kerning switch | CSS | **Placeholder** |
+| `font-optical-sizing` | `text`, `tspan` | Optical sizing mode | CSS | **Placeholder** |
+| `font-feature-settings` | `text`, `tspan` | OpenType feature-tag values | CSS | **Design** |
+| `font-variation-settings` | `text`, `tspan` | Variable-font axis values | CSS | **Design** |
+| `letter-spacing` | `text`, `tspan` | Letter spacing | CSS | **Placeholder** |
+| `word-spacing` | `text`, `tspan` | Word spacing | CSS | **Placeholder** |
+| `line-height` | `text`, `tspan` | Fixed or proportional line height | CSS | **Placeholder** |
+| `text-transform` | `text`, `tspan` | Case transformation | CSS | **Placeholder** |
+| `text-align` | `text` | Horizontal paragraph alignment | CSS | **Placeholder** |
+| `text-align-vertical` | Boxed `text` | Top, center, or bottom placement | Flutter | **Placeholder** |
+| `max-lines` | `text` | Paragraph line clamp | Flutter | **Placeholder** |
+| `text-overflow` | `text` | Clip, ellipsis, or authored marker string | CSS/Flutter | **Design** |
+| `text-decoration-line` | `text`, `tspan` | Underline, overline, or line-through | CSS | **Placeholder** |
+| `text-decoration-style` | `text`, `tspan` | Solid, double, dotted, dashed, or wavy | CSS | **Placeholder** |
+| `text-decoration-color` | `text`, `tspan` | Decoration color override | CSS | **Placeholder** |
+| `text-decoration-thickness` | `text`, `tspan` | Decoration thickness | CSS | **Placeholder** |
+| `text-decoration-skip-ink` | `text`, `tspan` | Glyph-ink skipping | CSS | **Placeholder** |
+
+The Draft 0 defaults are `font-size="16"`, `font-weight="400"`, and
+`font-style="normal"`. `size` is invalid and is never accepted alongside or
+as an alias for `font-size`; a canonical writer omits all three defaults on
+`text`. Every other style row above remains unknown syntax until its status
+advances; production defaults supply those unexposed `TextStyleRec` fields
+during materialization.
+
+Character data and direct `tspan` data concatenate in document order into one
+production `AttributedString`. Run boundaries are derived as UTF-8 byte
+offsets; authors do not spell them. Runs cover the entire string contiguously
+and merge when complete style and paint-override state match. Empty `tspan` is
+invalid, while an empty `text` uses the production `0..0` default-run special
+case.
+
+Run fills project `StyledTextRun.fills` rather than a parallel color field.
+Omitted run fill maps to `None` and node-fill fallback. The `fill` attribute
+maps to an explicit one-solid stack. A singular literal-first `` child
+uses the existing ordered paint grammar; `` maps to an explicit empty
+stack, and non-empty typed children remain bottom-to-top. Attribute and child
+are mutually exclusive. Gradient and image coordinates use the resolved full
+text-node paint box, never a per-`tspan` fragment box.
+
+HTML tags (`b`, `strong`, `i`, `em`, and similar), ``, and SVG `tspan`
+positioning are intentionally not aliases. The production run model contains
+visual style, not the semantic/accessibility annotation needed to preserve
+HTML meaning, and Grida run geometry does not independently position chunks.
+
+CSS `vertical-align` is intentionally not proposed for paragraph-box vertical
+placement: its web meaning concerns inline/baseline alignment. Flutter's
+`textAlignVertical` is the closer semantic precedent.
+
+## Effect placeholders
+
+The canonical scene model includes one layer blur, one backdrop blur, one
+liquid-glass effect, multiple shadows, and multiple noise effects. Candidate
+typed names are familiar, but the container grammar is not yet decided.
+
+| Candidate typed effect | Core properties | Inspiration | Status |
+| ---------------------- | ----------------------------------------------------- | ------------------ | ---------- |
+| `blur` | `radius`; progressive form also needs start/end radii | CSS/Flutter | **Design** |
+| `backdrop-blur` | `radius` or progressive blur parameters | CSS/Flutter | **Design** |
+| `drop-shadow` | `x`, `y`, `blur`, `spread`, `color`, `visible` | CSS/SVG | **Design** |
+| `inner-shadow` | `x`, `y`, `blur`, `spread`, `color`, `visible` | CSS/design tools | **Design** |
+| `noise` | size, density, octaves, seed, coloring, blend mode | Motion/photo tools | **Design** |
+| `liquid-glass` | light, refraction, depth, dispersion, blur parameters | Design tools | **Design** |
+
+An ordered `` list would be ergonomic, but the production model is
+not a single ordered heterogeneous list. The XML must not imply reorderable
+effect semantics until either the existing slots and lists receive a defined
+cross-type order or the engine model deliberately changes.
+
+## Syntax that needs dedicated design
+
+These are not ordinary missing attributes. Each needs a focused RFD because
+its structure, identity, or evaluation rules cannot be made reliable by
+choosing a name alone.
+
+| Area | Production requirement | Questions the syntax must answer |
+| ----------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
+| Editable vector network | Vertices, cubic tangents, segments, loops, filled regions | Stable local IDs or indices; open/closed contours; region fill stacks; validation |
+| Non-unit path reference box | Stable authored coordinate rectangle | Optional syntax; positive extents; normalization; canonical writer behavior |
+| Variable-width stroke | Ordered `(u, half-width)` samples | Nested stops versus compact list; interpolation; endpoints; base-width interaction |
+| Run strokes | Per-run stroke paints, width, and alignment | Reconcile singular production geometry with repeatable XML stroke topology |
+| Masks | Geometry, alpha, and luminance mask nodes | Which sibling is the mask; scope; consumption; stacking; clipping interaction |
+| Effects | Typed singleton and repeated effect slots | Cross-type order; multiplicity; compositing bounds; visibility and blend metadata |
+| Image resources and placement | Logical IDs, hashes, fit, affine placement, tiling | Packaging; base URI; network policy; intrinsic size; missing-resource behavior |
+| Boolean operations | Live child geometry and operation enum | Ordered operands; child ownership; nesting; flattening; paint inheritance |
+| Transform model | Native rotation, affine transforms, origin, source lens | One canonical authored form; layout-versus-paint transform; decomposition stability |
+| Components and reuse | Named definitions, `use`, scalar props, and named projection | Deep overrides; archive persistence; live-instance editing |
+| Animation | Time-varying values and motion-graphics behavior | Interpolation, timelines, expressions, easing, events, composition, serialization |
+| Embedded Markdown/HTML | Opaque render-only content | Escaping, sanitization, fonts/resources, intrinsic sizing, deterministic rendering |
+| Subobject identity | Paint, stroke, stop, run, vector, and lens-operation members | Stable member IDs; list edits; copy/paste; references; canonical writing |
+
+The vector-network and width-profile grammars are the clearest examples of
+where new syntax must be invented. Treating either as a JSON-shaped `data`
+attribute would hide structure, weaken diagnostics, and make hand or LLM
+authoring less reliable.
+
+## Current scene-contract seams to reconcile
+
+This registry does not itself change the canonical scene or archive contracts.
+The following seams constrain conformance or require the explicit extension
+named by the Draft 0 RFD:
+
+| Seam | Why it matters to Grida XML |
+| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| The current scene contract stores one stroke geometry with one ordered `Paints`, while Draft 0 repeats them. | One stroke maps directly; multiple strokes require an ordered list and must not be merged or hidden as duplicate scene nodes. |
+| The durable stroke-width boundary collapses four equal sides to a uniform or no-width state. | Draft 0 uses the same normalization and never emits an all-equal four-value canonical form. |
+| Production per-side coverage ignores corner smoothing and non-miter join choice. | Draft 0 rejects nonzero smoothing, round or bevel joins, and non-default miter limits on a per-side stroke rather than preserving lossy state. |
+| Production smooth corners circularize elliptical radius pairs. | Draft 0 rejects nonzero smoothing when any `rx` and `ry` differ; smoothed ellipses remain unavailable pending lossless renderer support. |
+| Production smooth paths begin dash traversal at the top-edge midpoint. | Draft 0 consistently begins every rectangle at the top-left curve's top-edge join; geometry is identical, but dashed smoothed strokes have a different phase. |
+| The scene model can hold malformed or backend-degenerate gradients that Draft 0 deliberately cannot spell. | A reader and writer reject too few stops, invalid or descending offsets, non-invertible transforms, and linear endpoint distances at or below `2^-15` instead of repairing, sorting, clamping, or perturbing them. |
+| Scene semantics and packed-archive coverage do not yet cover the same node set. | An authored element must not promise `.grida` round-trip until both contracts represent it. |
+| Scene and archive contracts expose both raw intrinsic paths and normalized box-mapped paths. | Draft 0 selects the box-mapped form; a raw-only target must gain a stable mapping or reject `path` rather than claim lossy support. |
+| One durable canonical-shape contract permits non-unit path data to be scaled to its tight fit. | Draft 0 deliberately rejects that fallback: non-unit geometry is invalid, and no reader may erase padding or remap untouched contours by auto-fitting it. |
+| Path fill-rule persistence is not uniform across scene and archive contracts. | Draft 0 requires `nonzero` and `evenodd` to survive reading, rendering, inspection, and rewriting; hard-coding one rule is not conforming. |
+| Draft 0 `hidden` removes a subtree from layout and paint. | A materializer must preserve that stronger behavior even when a target model names visibility differently. |
+| Solid-paint alpha is stored in color, while Draft 0 exposes common paint `opacity`. | The documented 8-bit quantization is a boundary rule, not a parallel solid-opacity property. |
+| Production `AttributedString` stores flat contiguous UTF-8 byte ranges. | XML concatenates mixed text in document order and derives ranges at character boundaries; authored offsets and nested runs are neither needed nor accepted. |
+| The packed archive collapses an empty run-fill vector to an absent override. | Draft 0 distinguishes explicit `` no ink from omitted node-fill fallback; packing must reject it or preserve presence before claiming round-trip. |
+| Production attributed paint currently resolves run fills against `(width, width)`. | Draft 0 requires the resolved full text-node width and height, matching node fills; the square paint box is an implementation bug, not XML semantics. |
+| `StyledTextRun` has one optional stroke stack plus one width and alignment. | It cannot losslessly project repeatable independent XML stroke geometries, so Draft 0 rejects run strokes rather than merging or hiding them. |
+| Production attributed runs carry visual style but no semantic/accessibility annotation. | Draft 0 rejects HTML semantic tags instead of pretending bold or italic styling preserves their meaning. |
+| Responsive bindings and per-child alignment carry more source intent than a resolved Cartesian scene. | A resolved scene can consume their result but must not be mistaken for a lossless rewrite of the authored source. |
+| Names, locks, durable identity, guides, and connections belong to different persistence concerns. | `id`, `name`, and `locked` need explicit authored-versus-editor rules; a root fill already covers visual background without deciding editor-only metadata. |
+| `lens` operations and native flips may lower through more general transforms. | Materialization can preserve pixels without promising a lossless inverse decomposition; source round-trip requires an intent-preserving representation. |
+
+When the language, runtime, and archive disagree, a processor must limit its
+claimed subset or make the model change explicit. It must never create a
+parallel XML-only property merely to conceal the incompatibility.
diff --git a/docs/wg/format/grida-xml.md b/docs/wg/format/grida-xml.md
new file mode 100644
index 0000000000..6917fa64f9
--- /dev/null
+++ b/docs/wg/format/grida-xml.md
@@ -0,0 +1,1722 @@
+---
+title: "Grida XML — authored scene language"
+description: "Open RFD for Draft 0 of .grida.xml, an inspectable XML source language for responsive 2D presentations and modern user interfaces."
+keywords:
+ - grida xml
+ - scene language
+ - intermediate representation
+ - 2d graphics
+ - layout
+ - text
+ - paint stacks
+ - gradients
+ - image fills
+ - multiple strokes
+ - component reuse
+ - multi-file rendering
+tags:
+ - internal
+ - wg
+ - format-schema
+ - canvas
+ - authoring
+ - layout
+ - text
+format: md
+---
+
+# Grida XML — authored scene language
+
+**Status:** Open RFD — Draft 0.
+
+**Authored file suffix:** `.grida.xml` — provisional.
+
+**Originating request:** [gridaco/grida#957](https://github.com/gridaco/grida/issues/957).
+
+## Thesis
+
+Grida XML is an authored, inspectable scene language for dynamic 2D content.
+It combines a small primitive-first graphics vocabulary with explicit box,
+binding, text-flow, and layout intent. Its target range includes both
+responsive presentations and modern user-interface designs.
+
+The language is XML because a tree is the scene's natural structure, XML is
+widely inspectable, and its element/attribute boundary is predictable for both
+human authors and language models. Grida XML borrows useful ideas from SVG,
+HTML/CSS, and widget trees without inheriting any one of their object models.
+It is its own vocabulary, not an SVG profile or an HTML serialization.
+
+Draft 0 is deliberately small. It establishes the document boundary, the box
+and nesting model, a minimal primitive set, ordered typed paint, multiple
+stroke geometries, per-side box strokes, rounded box geometry, text boxes with
+flat attributed runs, unit-space paths, free bindings, and flex layout. Later
+drafts can add effects, vector geometry, resource declarations, advanced
+typography, and reuse facilities without changing the central tree model. The
+selected multi-file direction is developed separately in the [Grida XML
+modules and static component reuse](./grida-xml-modules) RFD; none of that
+proposal is Draft 0 syntax. The proposed typed scalar API for those components
+is a further Version 2 delta in [Grida XML component
+parameters](./grida-xml-component-parameters). Exact named render projection is
+a Version 3 delta in [Grida XML component
+slots](./grida-xml-component-slots).
+
+## Design requirements
+
+1. **One obvious tree.** Outside explicitly typed property subtrees such as
+ `fill` and `stroke`, and the contextual `tspan` text-run element, element
+ nesting is scene nesting. Source order is painter order among sibling nodes,
+ stroke geometries, and paints within a channel. A node's children live in
+ that node's local coordinate space.
+2. **Composition instead of combined node kinds.** Every Draft 0 render
+ element except `text` can contain children. A rectangle containing text is
+ written as a `rect` containing a `text` element, not as a special
+ `ShapeWithText` kind.
+3. **A minimal primitive vocabulary.** One primitive has one semantic job.
+ Equal width and height make an ellipse a circle; there is no separate
+ `circle` kind.
+4. **Intent survives responsiveness.** Bindings, auto sizes, text constraints,
+ and layout relationships remain authored intent. They are not flattened to
+ the coordinates produced by one render.
+5. **Predictable authoring.** A current-version reader rejects unknown or
+ contradictory syntax. It never guesses, silently drops, or silently
+ reinterprets an authored construct.
+6. **Inspectable by default.** Canonical names are descriptive, values are
+ textual, and the file can be read or repaired without proprietary tooling.
+7. **Typed paint, not a parallel style language.** Fill channels and typed
+ paint values project the ordered Grida paint model directly. `solid`,
+ `gradient`, and `image` are semantic paint elements; structured values
+ remain structured. Each stroke geometry owns the same ordered paint value,
+ while repetition is a declared extension to the current production
+ single-geometry scene model.
+8. **Flat, explicit attributed text.** `text` owns the paragraph, default text
+ style, and node paint. A direct `tspan` child changes explicit kebab-case
+ run properties for only its own characters. It is neither a scene node nor
+ an embedded HTML or SVG positioning language.
+
+## File identity
+
+`.grida.xml` identifies the authored XML language defined here. The compound
+suffix is provisional, but it is intentionally distinct from `.grida`, the
+packed binary scene/archive format. The two files may describe equivalent
+scene intent, but they are different representations with different goals:
+
+| Representation | Primary property |
+| -------------- | ------------------------------------------ |
+| `.grida.xml` | Authored, inspectable, diffable XML source |
+| `.grida` | Packed binary storage and interchange |
+
+Changing or packing one representation into the other is a conversion. A
+processor must not infer Grida XML merely from a `.grida` suffix, nor infer the
+binary format from `.grida.xml`.
+
+## Document model
+
+A Draft 0 document has this shape:
+
+```xml
+
+
+
+
+
+
+```
+
+The XML declaration is optional; when present, it appears once and before all
+other content, declares XML `version="1.0"`, may declare only
+`encoding="UTF-8"`, and carries no other fields. The `grida` element is
+required and is only a document envelope: it has no box, paint, transform, or
+layout behavior. It must carry exactly `version="0"` and exactly one element
+child. That child must be a `container` and is the document's render root.
+Comments and formatting whitespace do not count as render roots.
+
+At resolution, the render root is the sole child of a definite, non-painting
+initial viewport container. Its `x` and `y` therefore use the ordinary binding
+grammar against that viewport. The explicit full-viewport form is
+`x="span 0 0" y="span 0 0"`; as with every span, the corresponding
+`width`/`height` and min/max constraints must be omitted. The same materialized
+scene may then resolve at different viewport extents without rewriting source
+intent.
+
+Omitted root bindings remain start-pinned at zero, and omitted or explicit
+`auto` sizes retain ordinary container-hug semantics. A fixed root extent is
+not scaled to the viewport; it may be smaller than or overflow that
+environment. `flow`, `grow`, and `align` remain invalid on the render root
+because the initial viewport container does not own flex layout.
+
+Draft 0 defines no XML namespace and no extension namespace. Namespace-based
+extensions require a later version rather than being accepted as inert data.
+Comments are allowed. CDATA sections, document types, and processing
+instructions are not; text uses ordinary escaped XML character data.
+
+### Core node taxonomy
+
+| Element | Box source | Render children | Meaning |
+| ----------- | ------------------------------- | --------------- | --------------------------------------------------- |
+| `container` | Declared; `auto` may hug | Yes | Boxed composition and the sole flex-layout owner |
+| `rect` | Declared | Yes | Parametric rectangle realized in its box |
+| `ellipse` | Declared | Yes | Parametric ellipse realized in its box |
+| `line` | Declared width; zero-height box | Yes | Horizontal local line, orientable by rotation |
+| `path` | Declared | Yes | SVG path commands in a fixed unit reference box |
+| `text` | Measured under box constraints | No | Unicode text with optional flat attributed runs |
+| `group` | Derived from child bounds | Yes | Logical subtree with an explicit local origin |
+| `lens` | Derived from child bounds | Yes | Group whose ordered operations affect painting only |
+
+`text` accepts no render-node children. It alone may contain direct, flat
+`tspan` contexts in addition to character data and its leading paint
+properties. `tspan` is not a render node and does not have a box, identity,
+transform, opacity, layer position, or child scene. Every other node accepts an
+ordered list of render-node children, including each primitive element. A
+paintable parent paints its fill first, then its children in document order,
+then its own strokes. Later siblings paint above earlier siblings.
+
+`shape` is a reserved element name, not a Draft 0 render element. It is held
+for a possible future custom-shape definition. This reservation assigns no
+box, paint, child, definition, or reuse behavior. A Draft 0 reader **MUST**
+reject ``, including historical ``. The bounded
+`kind` discriminator belongs only to the `gradient` paint family; it is not a
+render-node attribute.
+
+The nesting contract is normative. This is a regular rectangle with a text
+child:
+
+```xml
+
+ Continue
+
+```
+
+The text is positioned in the rectangle's local 240 by 64 box. No combined
+shape-with-text node exists or is implied.
+
+## Scalar and coordinate conventions
+
+- Numbers are finite decimal values with no unit suffix. Geometry numbers are
+ logical pixels except where a property defines a normalized reference
+ space; path data uses the fixed unit rectangle from `(0, 0)` through
+ `(1, 1)`.
+- The local origin is the top-left of a boxed node. Positive x points right;
+ positive y points down.
+- Widths and heights are non-negative. Binding offsets may be negative.
+- Angles are degrees, increasing clockwise in the y-down coordinate system.
+- Boolean values are exactly `true` or `false`.
+- Authored colors use `#RGB` or `#RRGGBB`; a canonical writer emits uppercase
+ `#RRGGBB`. Alpha belongs to `opacity`, not to the color token. Richer color
+ spaces are future work.
+
+## Property registry
+
+The [Grida XML property registry](./grida-xml-properties) is the canonical
+cross-draft inventory of element and property names, their valid targets, and
+production-backed placeholders. This RFD remains the normative Draft 0
+grammar. A property marked Placeholder or Design in the registry is therefore
+still unknown syntax to a Draft 0 reader.
+
+## Common authored attributes
+
+Only attributes applicable to an element may appear on that element.
+
+| Attribute | Value | Default | Contract |
+| ------------------------- | ----------------------------------------- | ---------- | ---------------------------------------------------------------- |
+| `name` | XML string | none | Human-readable label; it is not durable node identity |
+| `x`, `y` | position binding | `0` | Places a free-positioned node in its parent's local box |
+| `width`, `height` | finite number or `auto`, where applicable | by element | Declares a fixed extent or requests element-specific measurement |
+| `min-width`, `min-height` | non-negative finite number | none | Sets a lower bound on a non-derived box axis |
+| `max-width`, `max-height` | non-negative finite number | none | Sets an upper bound on a non-derived box axis |
+| `aspect-ratio` | positive finite `width:height` pair | none | Resolves one otherwise under-specified box axis |
+| `corner-radius` | one or four radii, optionally elliptical | `0` | Rounds the outline of a `container` or `rect` |
+| `corner-smoothing` | finite number in `[0, 1]` | `0` | Smooths circular corners on a `container` or `rect` |
+| `rotation` | finite number | `0` | Visual rotation about box center; about origin for derived kinds |
+| `flip-x`, `flip-y` | `true` or `false` | `false` | Reflects paint horizontally or vertically before rotation |
+| `fill` | `#RGB` or `#RRGGBB` | none | Canonical compact form for one ordinary solid fill |
+| `d` | SVG path-data string | required | Defines a `path` in the fixed unit reference rectangle |
+| `fill-rule` | `nonzero` or `evenodd` | `nonzero` | Selects path interior construction |
+| `font-size` | positive finite number | `16` | Default size on `text`; optional run override on `tspan` |
+| `font-weight` | integer from `1` through `1000` | `400` | Default weight on `text`; optional run override on `tspan` |
+| `font-style` | `normal` or `italic` | `normal` | Default style on `text`; optional run override on `tspan` |
+| `opacity` | finite number in `[0, 1]` | `1` | Composites the node and its descendants |
+| `hidden` | `true` or `false` | `false` | Removes the subtree from layout and painting |
+| `flow` | `in` or `absolute` | `in` | Opts a child into or out of a flex parent's flow |
+| `grow` | non-negative finite number | `0` | Flex main-axis growth factor |
+| `align` | `start`, `center`, `end`, or `stretch` | none | Per-child flex cross-axis override |
+
+`width` and `height` are the only Draft 0 spellings. The same rule applies to
+`min-width`, `max-width`, `min-height`, `max-height`, and `aspect-ratio`.
+Short experimental spellings such as `w`, `h`, `min-w`, and `aspect` are not
+Grida XML. Likewise, `container` is the node name; `frame` is not a Grida XML
+alias. Historical `` is TextIr syntax, not a Grida XML alias.
+A host may support that import dialect separately, but a conforming Draft 0
+writer never emits it and a strict Grida XML reader does not confuse it with
+this language.
+
+Rotation is visual-only intent. It does not change a node's layout box, flex
+contribution, or a container's hug size; the resolved visual bounds do include
+the rotated paint. Flips have the same visual-only status and use the same
+pivot rule as rotation. A node's local paint is reflected first and then
+rotated; neither operation writes a matrix or negative extent into source.
+
+`fill="#fff"` means exactly one visible, fully opaque, normal-blend solid
+paint. It is not legacy syntax: it is the canonical compact form for the most
+common fill. On a render node it cannot coexist with the structured ``
+channel. Node `fill` is valid on `container`, `rect`, `ellipse`, `path`, and
+`text`; it is invalid on `line`, `group`, and `lens`. Contextually, `fill` on `tspan` is a
+single-solid run override rather than a second node paint channel. Omitting it
+leaves the run attached to the `text` node's fill stack.
+
+## Paint channels and vocabulary
+
+The XML topology separates where paint is applied from what the paint is:
+
+```text
+render node
+├─ fill attribute or fill channel → ordered Paint*
+└─ stroke geometry* → ordered Paint*
+
+Paint := solid | gradient(kind) | image
+```
+
+`fill` is the node's single fill channel. `stroke` is a repeatable stroke
+geometry, and each occurrence owns one paint channel. Neither is a render node
+or an extra compositing layer. Typed paint children are ordered bottom to top:
+the first is painted first, and each later paint composites above the
+accumulated result.
+
+A contextual `tspan` may carry one optional run-fill override using the same
+`fill` attribute-or-channel partition and the same ordered `Paints` value. It
+does not introduce a second paint abstraction. Omission means node-fill
+fallback rather than an independent run default.
+
+This is the canonical expanded form for a rich fill:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+ Example
+
+```
+
+A node's structural paint children must precede character data and render-node
+children. When both channels occur, the optional `fill` element comes first,
+followed by zero or more `stroke` elements. `container`, `rect`, `ellipse`,
+`path`, and `text` accept fills and strokes. `line` accepts strokes only.
+`group` and `lens` accept neither. The contextual `tspan` exception accepts one
+`fill` as its literal first child and no `stroke`.
+
+The direct paint vocabulary is:
+
+| Element | Paint value |
+| ---------- | -------------------------------------------------------------------------- |
+| `solid` | One color with alpha carried by paint opacity |
+| `gradient` | A structured stop ramp with `linear`, `radial`, `sweep`, or `diamond` kind |
+| `image` | A referenced image placed into the node's paint box |
+
+The element name carries the paint type. `gradient` is one bounded paint
+family whose required `kind` selects the existing gradient variant; this is
+not a generic `` design. ``, ``,
+``, and the four kind-specific gradient element names are not
+aliases. `solid` and `image` are empty elements. `gradient` is non-empty
+because its direct children are the structured stop ramp.
+
+### Fill defaults, emptiness, and canonical shorthand
+
+Omitting both the `fill` attribute and the `` child element applies the
+element's language default:
+
+| Element | Default fill paint stack |
+| --------------------------------- | ----------------------------------- |
+| `container` | empty |
+| `rect`, `ellipse`, `path`, `text` | one opaque normal-blend black solid |
+| `line`, `group`, `lens` | not fillable |
+
+`tspan` has no independent language-default paint stack. Omitted run fill
+means `None` and therefore uses the owning `text` node's effective fill stack;
+an explicit empty run fill means no glyph ink for that run.
+
+`` differs from omission on primitives and text: it suppresses the
+default black paint. A reader also accepts a structured one-solid channel, but
+there is one canonical writer representation. For a node fill, the writer
+applies these rules in order:
+
+1. If the fill stack equals the element default, omit both forms.
+2. If it is exactly one visible, fully opaque, normal-blend solid, emit the
+ `fill="#RRGGBB"` attribute.
+3. For every other non-empty stack, emit one `fill` element with typed paint
+ children in bottom-to-top order.
+4. If the stack is empty but omission would restore a non-empty default, emit
+ ``.
+
+The two fill forms are mutually exclusive. A processor must never interpret
+the attribute as an implicit bottom paint beneath an expanded channel.
+
+### Multiple stroke geometries
+
+One stroke paint stack and multiple stroke geometries are different
+capabilities. Multiple paints within one `stroke` share one coverage mask:
+they have the same width, alignment, cap, join, and dash pattern. Repeated
+`stroke` elements generate independent coverage masks over the same underlying
+node outline, and each mask has its own ordered paint stack.
+
+This distinction solves designs that a single SVG stroke cannot represent
+directly: road casings, double borders, concentric keylines, focus rings plus
+borders, and decorative inner and outer outlines. SVG or CSS authors commonly
+duplicate geometry, nest wrappers, combine `border` and `outline`, introduce
+pseudo-elements, or approximate an outline with shadows. Those workarounds
+split one semantic path across several scene objects, make geometry edits
+drift, and often disagree at joins, dashes, transforms, or hit bounds. Native
+multi-stroke keeps one shape, one layout identity, and one editable outline.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+The Draft 0 stroke geometry is:
+
+| Attribute | Value | Default | Valid targets |
+| ------------- | ------------------------------------------ | --------------------------------------- | ------------------------------------------------ |
+| `width` | one or four non-negative finite pixels | `1` | every `stroke`; four only on `container`, `rect` |
+| `align` | `inside`, `center`, or `outside` | `inside`; `center` on `line` and `path` | every `stroke`; open geometry must be `center` |
+| `cap` | `butt`, `round`, or `square` | `butt` | `line`, `path` |
+| `join` | `miter`, `round`, or `bevel` | `miter` | `container`, `rect`, `path` |
+| `miter-limit` | positive finite number | `4` | `container`, `rect`, `path` |
+| `dash-array` | space-separated non-negative finite values | absent, meaning solid | `container`, `rect`, `ellipse`, `line`, `path` |
+
+When present, `dash-array` contains at least one value and is not all zero.
+Odd-length arrays repeat once to make an even dash-gap cycle. An attribute on a
+target outside this table is an error rather than dormant state. A `line` is an
+open path and accepts only `align="center"`, because inside and outside are not
+defined for it. A `path` has the same centered default; `inside` or `outside`
+is valid only when every contour is closed. Variable-width profiles, dash
+offset, and endpoint markers are later vocabulary.
+
+#### Uniform and per-side width
+
+The `width` attribute is the lexical form of the existing stroke-width choice;
+it is not a second geometry abstraction. One number is a uniform width.
+Exactly four numbers are rectangular widths in top, right, bottom, left order:
+
+```xml
+
+
+
+
+
+
+
+```
+
+Every number is a non-negative finite logical-pixel value. Lists of two or
+three numbers, commas, units, and empty lists are invalid. The four-value form
+is valid only on `container` and `rect`, even when all four values happen to be
+equal; applicability is checked before value normalization. Side names refer
+to the node's untransformed local box and are not permuted in source by flips,
+rotation, or ancestor transforms.
+
+Omitted `width` means the uniform default `1`. A reader normalizes four equal
+positive values to the uniform state and four zero values to the no-width
+state. A canonical writer omits uniform `1`, writes every other positive
+uniform width as one number, writes a present no-width state as `width="0"`,
+and writes four values only when they differ. Thus `width="3 3 3 3"` is
+accepted on a box but canonicalizes to `width="3"`; `width="0 0 0 0"`
+canonicalizes to `width="0"`. A zero-width painted stroke remains authored
+intent and is not deleted merely because it has no current coverage.
+
+Each repeated `stroke` owns its width independently. A four-value width does
+not alter, inherit from, or accumulate with any earlier stroke. Its direct
+typed paints all share one resulting ring; Draft 0 does not attach separate
+paint stacks to the four sides.
+
+Let `t`, `r`, `b`, and `l` be the resolved top, right, bottom, and left widths.
+Alignment supplies the outward and inward fractions used to construct the
+stroke's outer and inner contours:
+
+| Alignment | Outward fraction | Inward fraction |
+| --------- | ---------------- | --------------- |
+| `inside` | `0` | `1` |
+| `center` | `1/2` | `1/2` |
+| `outside` | `1` | `0` |
+
+The outer box expands each local side by its width times the outward fraction;
+the inner box insets it by its width times the inward fraction. At a corner
+with horizontal-adjacent width `h`, vertical-adjacent width `v`, and source
+radius `(rx, ry)`, the two contour radii are:
+
+- outer: `(rx + outward * h, ry + outward * v)`;
+- inner: `(max(0, rx - inward * h), max(0, ry - inward * v))`.
+
+For example, the top-left corner uses `h = l` and `v = t`; the bottom-right
+uses `h = r` and `v = b`. Each outer and inner contour then applies the
+ordinary rounded-rectangle non-overlap rule. These operations resolve
+coverage only; they never rewrite the four authored widths or resize the
+layout box.
+
+Responsive geometry can resolve smaller than its authored stroke widths, so
+overconsumption is not a parse error. If either inner-box extent is zero or
+negative after the insets, the inner contour is empty and coverage saturates
+to the outer contour instead of producing inverted geometry.
+
+Per-side widths support every stroke alignment, ordinary circular or
+elliptical corner radii, and `dash-array`. Dash traversal remains one
+continuous clockwise contour. A zero-width side suppresses coverage on that
+side but does not remove its length from the dash metric or restart the dash
+pattern.
+
+Two current compatibility boundaries are strict rather than lossy:
+
+- a per-side width requires `corner-smoothing="0"`, because the existing
+ per-side ring cannot follow a smoothed source outline;
+- it admits only the default `join="miter"` and `miter-limit="4"`, because
+ round and bevel joins and non-default miter limits are not independently
+ represented by the existing rectangular ring.
+
+These restrictions apply even when a conflicting value would currently have
+no visible effect. A reader or writer must reject the combination rather than
+retain state that rendering ignores. Uniform-width strokes retain the full
+`join` and `miter-limit` grammar already defined above.
+
+##### Considered width syntax
+
+1. **One-or-four values on `width` — accepted.** It preserves the common
+ scalar form, makes rectangular width a surgical one-token-per-side edit,
+ and maps directly to the uniform-or-rectangular model choice.
+2. **A base width plus named side overrides — rejected as canonical.** It
+ exposes unresolved fallback state and permits many equivalent spellings.
+3. **Four `top-width`, `right-width`, `bottom-width`, and `left-width`
+ attributes — rejected.** They are explicit but disproportionately verbose
+ for four scalar values and would introduce a second width vocabulary.
+4. **A nested `width` or `sides` element — rejected.** It adds structural
+ geometry among direct paint children without representing another model
+ object.
+5. **CSS two- and three-value expansion — rejected.** Draft 0 requires either
+ one value or all four so no value is duplicated by an implicit positional
+ rule.
+6. **A separate `widths` attribute — rejected.** Singular and plural names for
+ one model choice would weaken the one-spelling rule.
+
+Uniform stroke width is distributed relative to the source outline. `center`
+places half the width on each side; `inside` places the full width toward the
+filled region; `outside` places it away from the filled region. For text, that
+region is each shaped glyph's filled outline, including the inverse direction
+around counters. Line caps and uniform rectangular joins use the declared
+`cap`, `join`, and `miter-limit` values.
+
+Dash traversal begins at the line's local start point, at a rectangle's
+top-left corner moving clockwise, and at an ellipse's rightmost point moving
+clockwise. On a rounded rectangle, the rectangular origin becomes the point
+where the top-left corner curve joins the top edge, still moving toward the
+top-right. The first dash begins at that origin because Draft 0 has no dash
+offset. Each distinct contour restarts the pattern. A zero-width side of a
+per-side stroke still advances this traversal before the following side.
+
+Stroke elements are ordered in painter order: the first stroke is bottommost
+and the last is topmost. Paints within each stroke are independently ordered
+bottom to top. A node paints its fill stack, then its render-node children, then
+all stroke geometries. Thus children remain below their parent's border-like
+strokes. Each stroke is derived independently from the same original outline;
+a later stroke is not offset from, clipped by, or inherited from an earlier
+stroke.
+
+Omitting all `stroke` elements means no strokes. A stroke normally contains at
+least one typed paint child. An empty stroke is valid only when it carries at
+least one geometry value that differs from that target's defaults; it preserves
+dormant geometry that the current model can distinguish. A default empty
+`` is invalid because the model has no presence bit that distinguishes
+it from omission. A zero-width or wholly invisible painted stroke likewise
+remains authored intent and must not be deleted or reordered by a canonical
+writer.
+
+#### Accepted model extension: stroke geometry multiplicity
+
+The current production Grida scene model has one ordered stroke `Paints` value
+plus one shared stroke geometry per node. One XML `stroke`, when its attributes
+apply to the target node, projects that pair directly. Two or more stroke
+elements cannot: merging their paint lists would erase geometry, while
+duplicating the scene node would erase the one-shape semantic contract.
+
+Draft 0 therefore deliberately widens this part of the scene contract to an
+ordered list of stroke geometries, each owning `Paints`. The language contract
+is accepted ahead of that production scene/archive change; it is a declared
+required model extension, not permission to retain a permanent XML-only list.
+Until a materializer's target model can represent the list, it must reject the
+second stroke with an unsupported-model diagnostic; it must not keep only one,
+merge them, or create hidden duplicate nodes.
+
+### Coverage and paint boxes
+
+Paint coverage and paint coordinates are separate. Coverage determines which
+pixels a channel may affect; the paint box supplies the local coordinate space
+for gradients and image placement.
+
+| Target | Fill coverage | Stroke source outline | Paint box |
+| ------------------- | ----------------------------------- | ---------------------------------- | --------------------------------------------- |
+| `container`, `rect` | rectangular interior | rectangular perimeter | resolved local layout box |
+| `ellipse` | elliptical interior | elliptical perimeter | resolved local layout box |
+| `path` | box-mapped path fill region | box-mapped path contours | resolved local layout box |
+| `line` | none | authored local line segment | resolved segment box with degenerate fallback |
+| `text` | shaped glyph interiors, not its box | shaped glyph contours, not its box | resolved local text layout box |
+
+The paint box is resolved after bindings, flex sizing, constraints, and text
+measurement, but before the node's visual rotation or flips. Children do not
+change their parent's coverage, outline, or paint box. A zero-length paint-box
+axis is replaced for paint-coordinate mapping only by a one-logical-pixel
+interval centered on that axis; the node's actual geometry and layout extent
+remain zero. For the resulting effective box `(x, y, width, height)`, unit paint
+coordinates map through
+`translate(x,y) × scale(width,height) × paint-transform`.
+
+Stroke paints use the same box as fills, so changing stroke width does not
+rescale a gradient or image. Paint is evaluated in that box and then clipped by
+the individual stroke coverage. A solid extends throughout that coordinate
+space. Gradient samples outside the unit box follow the gradient kind's tile or
+clamp rule. An image is transparent outside the image rectangle produced by
+its `fit`, so an outside-aligned stroke may sample transparency beyond the node
+box.
+
+Strokes do not grow layout bounds. Resolved visual bounds conservatively include
+the coverage of effective strokes: a stroke must have positive width and at
+least one visible, non-zero-opacity paint. Those bounds may contain transparent
+image or gradient samples; exact non-transparent pixel bounds are not required.
+Centered and outside-aligned effective strokes may extend the conservative
+visual bounds beyond the layout box.
+
+#### Conformance on degenerate paint boxes
+
+Draft 0's one-pixel paint-space fallback is required for rich paint on
+degenerate geometry. A materializer that lacks this mapping must report the
+case as unsupported rather than silently substitute a different gradient or
+image mapping.
+
+### Common paint properties
+
+Every typed paint accepts the same compositing properties:
+
+| Attribute | Value | Default | Meaning |
+| ------------ | ---------------------------------- | -------- | ------------------------------------------- |
+| `visible` | exactly `true` or `false` | `true` | Whether this paint participates in painting |
+| `opacity` | finite number in `[0,1]` | `1` | Alpha applied to this paint only |
+| `blend-mode` | one of the paint blend modes below | `normal` | How this paint composites over paints below |
+
+Visibility is stored intent. A false paint remains in the ordered list but
+does not draw. Zero opacity likewise has no visual contribution; neither case
+authorizes a writer to delete or reorder the paint.
+
+Paint opacity is distinct from node opacity. Paint opacity affects only that
+entry while the node's `opacity` composites the node and its descendants as a
+group. Moving a value between these two levels is not a semantic-preserving
+normalization.
+
+Paint blend modes are `normal`, `multiply`, `screen`, `overlay`, `darken`,
+`lighten`, `color-dodge`, `color-burn`, `hard-light`, `soft-light`,
+`difference`, `exclusion`, `hue`, `saturation`, `color`, and `luminosity`.
+`pass-through` is a layer mode, not a paint blend mode. Each paint's blend mode
+applies while that paint composites over the accumulated paints below it; it
+does not retroactively alter an earlier paint.
+
+#### Conformance on stack compositing
+
+Collapsing a paint stack into one shader is not equivalent to the ordered model
+for every stack. For example, a bottom `multiply` paint followed by a
+translucent normal paint must multiply only the bottom entry; applying
+`multiply` to a combined result also changes the later entry.
+
+Draft 0 follows the `Paints` model: each visible entry composites in order with
+its own blend mode against the actual accumulated result. An implementation
+may collapse the operations only when it proves the result equivalent. This is
+a quarantined renderer incompatibility, not grounds to change paint order,
+move blend metadata, or fork the paint model.
+
+### Solid colors and alpha quantization
+
+`solid` requires `color`. Its authored `opacity` is represented as an 8-bit
+color alpha in the common paint model, so parsing quantizes opacity to the
+nearest alpha byte:
+
+```text
+stored opacity = round(authored opacity × 255) / 255
+```
+
+When a canonical writer emits a structured solid or stop opacity, it emits that
+stored value. For example, authored `0.5` normalizes to `128/255`, approximately
+`0.5019608`. This is an intentional projection of the common paint model, not
+an independent floating-point solid opacity. A stop's optional `opacity` uses
+the same 8-bit quantization for its color alpha. A gradient or image paint's
+own opacity remains independent of the colors or pixels it modulates.
+
+### Structured gradients
+
+Every `gradient` requires exactly one `kind`, one of `linear`, `radial`,
+`sweep`, or `diamond`. These values map one-for-one to the existing Grida paint
+variants; the family element does not collapse them into a new runtime type.
+The element contains at least two empty `stop` children. It permits comments
+and formatting whitespace, but no other element or non-whitespace character
+data. A stop requires a finite `offset` in `[0,1]` and a color, and may carry
+`opacity`; it does not accept paint-level visibility or blend attributes. Stops
+appear in nondecreasing offset order. Equal adjacent offsets are valid and
+express a hard transition; a reader must preserve their source order and must
+not sort, merge, or deduplicate them.
+
+The gradient-kind attribute boundary is strict:
+
+| Attribute | `linear` | `radial` | `sweep` | `diamond` |
+| ------------ | -------- | -------- | -------- | --------- |
+| `kind` | required | required | required | required |
+| `transform` | yes | yes | yes | yes |
+| `from`, `to` | yes | no | no | no |
+| `tile-mode` | yes | yes | no | no |
+
+Changing `kind` preserves stops, `transform`, and the common paint properties.
+Attributes that do not apply to the new kind must be removed; they never remain
+as dormant state. The discriminator edit is localized, but a kind switch with
+variant-specific attributes also requires that explicit cleanup, and switching
+back does not restore discarded state.
+
+All gradient geometry is defined in the unit paint box `[0,1] × [0,1]`, with
+`(0,0)` at top-left and `(1,1)` at bottom-right. The optional
+`transform="a b c d e f"` is a finite, mathematically invertible affine
+transform in that unit space, represented as:
+
+```text
+[ a c e ]
+[ b d f ]
+```
+
+It defaults to identity. The effective paint-box mapping defined above composes
+its translation and scale with this transform, including the declared
+degenerate-axis fallback, so gradient geometry remains independent of the
+node's pixel size.
+
+Mathematical invertibility is the authored-state invariant. Once layout has
+produced the actual paint box, an evaluator must also verify that the exact
+paint-box/transform composition is invertible in its backend representation.
+Extreme finite transforms can be representable for one box and not another;
+box scale may rescue or defeat their numerical conditioning. An evaluator
+must also prove that its selected implementation can construct every gradient
+kind, including custom/runtime-effect kinds. It must report any contextual
+failure before drawing the frame. It must not rewrite the transform, omit the
+gradient, or treat the source document as syntactically invalid. Applying one
+uniform capability rule to every gradient kind preserves kind-switch behavior;
+it does not claim that every backend kind reaches failure through the same
+factory path.
+
+A linear gradient additionally carries `from="u v"` and `to="u v"`. They
+default to `0 0.5` and `1 0.5`. Each requires exactly two finite values; values
+outside `[0,1]` are allowed to place an endpoint outside the paint box. After
+the model's centered-alignment lowering and binary32 unit-space projection,
+the endpoint distance must be greater than `2^-15`. Coincident, quantized-
+coincident, and shorter ramps are invalid because the current proving backend
+otherwise replaces them with a degenerate color shader before applying the
+declared transform. They are first-class linear-kind state, not a second
+spelling of the transform.
+
+The remaining base geometries are fixed before the transform:
+
+| Type | Unit-space ramp domain |
+| ------- | --------------------------------------------------------------------------- |
+| radial | center `(0.5,0.5)`, radius `0.5`; `t` grows from center to radius |
+| sweep | center `(0.5,0.5)`; `t=0` points right and grows clockwise through one turn |
+| diamond | `t = min(1, 2 × (abs(u-0.5) + abs(v-0.5)))`, the Manhattan/L1 distance ramp |
+
+Linear and radial gradients accept `tile-mode`, one of `clamp`, `repeated`,
+`mirror`, or `decal`, defaulting to `clamp`. Sweep and diamond gradients are
+clamped and reject that attribute in Draft 0.
+
+| Tile mode | Samples outside the gradient domain |
+| ---------- | ----------------------------------------------------- |
+| `clamp` | extend the nearest edge color |
+| `repeated` | repeat the ramp in the same direction |
+| `mirror` | repeat the ramp, reversing every alternate repetition |
+| `decal` | become transparent |
+
+#### Linear endpoint numeric seam
+
+The common paint model stores linear endpoints as binary32 centered-normalized
+alignments. The Draft 0 boundary evaluates the authored UV conversion
+`alignment = 2 × uv - 1` in binary64 and then stores binary32; a canonical
+writer computes the inverse in binary64 and verifies that reparsing recovers
+the exact binary32 model value. This preserves ordinary arbitrary model values,
+including centered-normalized values such as `0.1`, without redefining the
+paint model.
+
+A microscopic subset of finite binary32 alignments too close to centered zero
+cannot survive that binary64 UV conversion. The smallest positive binary32
+subnormal is one example: adding its inverse UV delta to `0.5` rounds back to
+exactly `0.5`. A canonical writer must reject such an endpoint with a precise
+representability error rather than normalize it to center. This is an explicit
+format/model compatibility limit, not a restriction on `Paint`; a future
+arbitrary-precision source-number boundary may remove it without changing the
+paint model or XML vocabulary.
+
+#### Compatibility quarantine: unconstrained gradient model state
+
+The current paint data types can hold gradient values that Draft 0 deliberately
+rejects: fewer than two stops, out-of-range or descending stop offsets,
+mathematically non-invertible transforms, and linear endpoints at or below the
+declared degeneracy threshold. Those states have no valid Grida XML spelling. A
+canonical writer must report the failed invariant and must not invent stops,
+sort or clamp offsets, replace a transform, or perturb an endpoint. Complete
+model-to-XML round-trip requires these invariants to be established in the
+common paint model first. Backend failure after composition with a pathological
+resolved paint box is not part of this quarantine: the authored gradient
+remains valid and frame evaluation reports the contextual capability failure.
+
+#### Compatibility quarantine: gradient editing model
+
+The current paint contract and the
+[Gradient Session](../canvas/paint-session/gradient.md) RFD disagree in two
+places that are not safe to average:
+
+- Grida XML and the paint contract store a linear gradient's endpoints
+ explicitly, while the session RFD says all non-trivial orientation belongs
+ only in the transform.
+- Grida XML and the paint contract define diamond by Manhattan/L1 distance,
+ while the session RFD describes an L-infinity distance field.
+
+For Draft 0 files, the definitions in this RFD are normative. A processor must
+not silently rewrite explicit endpoints into a transform or substitute the
+other diamond metric. The session model remains quarantined at this seam until
+the two documents are reconciled together; changing either serialized meaning
+requires an explicit compatibility decision.
+
+### Image paints and resources
+
+`image` directly inside `fill` or `stroke` is an image paint. It requires a
+non-empty `src`, which is an authored resource identifier, and accepts
+`fit="cover|contain|fill|none"`. The default is `cover`; `contain` preserves
+aspect ratio within the box, `cover` preserves aspect ratio while covering and
+cropping, `fill` stretches to both box axes, and `none` retains intrinsic image
+size. Draft 0 centers the placed image and defines no image-paint syntax for
+orientation, filters, free-transform placement, or tiling.
+
+`src` materializes as a logical resource ID (RID), preserving the authored
+string. Draft 0 does not author content-hash resource references through
+`image`; adding another resource-identity form requires explicit syntax rather
+than guessing from the string's shape.
+
+Syntactic parsing preserves `src` and performs no file, network, or decode
+operation. Resource resolution belongs to the explicit resolution environment:
+
+- a relative identifier such as `./noise.png` resolves against the document's
+ base location;
+- an absolute logical resource identifier is looked up as authored;
+- the authored string is not rewritten merely because a host found its bytes;
+- a missing, unreadable, or undecodable image is a resource-resolution failure,
+ not malformed XML.
+
+A strict materializer reports the failing authored identifier and, when one
+exists, its resolved location. It must not silently discard the image paint and
+claim a complete resolved result. An interactive host may display a diagnostic
+placeholder while keeping the unresolved state explicit.
+
+Image sampling also has an evaluation-time numeric condition. Immediately
+before drawing, a strict evaluator composes the requested view, the image
+owner's resolved world transform, and the image-fit transform using the
+backend's numeric representation. When the geometry transform itself is
+invertible but that complete sampling matrix is not, evaluation fails with the
+node, paint context, and resource identifier before any frame pixels are
+emitted. A different view may rescue or defeat an extreme fit matrix, so this
+check cannot be reduced to XML validation or permanently attached to a view-
+independent frame product. Singular geometry transforms remain valid collapsed
+coverage; they are not reported as image failures merely because they have no
+2D inverse.
+
+The meaning of `image` is contextual. Inside `fill` or `stroke` it is always a
+paint. Outside those channels it occupies the scene-element namespace, where a
+later version may define an image node. Draft 0 defines no scene-image node and
+therefore rejects an out-of-context `image` rather than confusing it with a
+paint.
+
+### Inline definitions and future reuse
+
+Typed paint elements are canonical inside an expanded channel, with the
+singleton ordinary-solid `fill` attribute as the compact exception. Draft 0
+defines no reusable paint definition, reference, identifier, or lookup scope.
+Paint definitions and references are future work and are not required to
+express any current fill or stroke.
+
+### Considered syntax alternatives
+
+1. **Direct typed paint children under `fill` and `stroke` — accepted.** Paint
+ type and painter order remain visible in ordinary XML structure.
+2. **`` — accepted.** The four current variants share stops,
+ transform, visibility, opacity, and blend behavior. The bounded discriminator
+ localizes a geometry switch while keeping `gradient` a semantic paint type;
+ incompatible kind-specific attributes still require removal.
+3. **Four gradient element names — rejected for Draft 0.** They duplicate the
+ shared family grammar and make a geometry switch replace the wrapper. A
+ structurally different future gradient, such as a mesh, should receive its
+ own semantic element instead of being forced into this family.
+4. **A generic paint or repeated fill-layer envelope — deferred unless layer
+ metadata becomes independent from its source.** Common visibility, opacity,
+ and blend properties currently belong to the typed paint itself, so
+ `` would add a level without representing a distinct
+ model concept.
+5. **Plural ``/`` or `` channels — rejected.**
+ Singular `fill` names the one fill channel, while repeatable `stroke` names
+ one geometry with its paints. The hierarchy already supplies the target;
+ repeating it in a generic container costs clarity and tokens.
+6. **CSS-like functional attributes — rejected as canonical.** Encoding ramps
+ and image placement inside attribute strings creates a second nested
+ grammar, produces poorer diagnostics, and is less reliably authorable by
+ language models.
+
+### Position bindings
+
+`x` binds the horizontal axis and `y` binds the vertical axis. For a boxed or
+measured node with parent extent `E` and resolved child extent `s`, the value
+forms are:
+
+| Form | Meaning | Resolved start |
+| ---------------- | ----------------------------------- | ------------------------- |
+| `n` or `start n` | Offset from the parent's start edge | `n` |
+| `end n` | Offset from the parent's end edge | `E - n - s` |
+| `center` | Center in the parent | `(E - s) / 2` |
+| `center n` | Center, then apply an offset | `(E - s) / 2 + n` |
+| `span a b` | Bind both edges | start `a`, extent `E-a-b` |
+
+The bare number is the canonical spelling for a start binding. `span` owns
+the size on its axis, so `width`, `min-width`, and `max-width` must be omitted
+when `x` is a span; the corresponding height attributes must be omitted when
+`y` is a span. End, center, and span bindings require a resolvable parent
+extent; using one against an unresolved auto or derived parent axis is a
+resolution error, not an invitation to guess.
+
+If a resolved span would have negative extent because `a + b > E`, its extent
+is clamped to zero at start `a` and the resolver reports that clamp. This is a
+valid resolved boundary case, not a negative box and not a source rewrite.
+
+For `group` and `lens`, bindings place the node's local origin rather than the
+top-left of its derived union. Their start, end, and center formulas are `n`,
+`E - n`, and `E / 2 + n`, respectively; child-union size `s` is not
+subtracted. `span` is invalid on a derived-box node because there is no
+authored axis extent for it to own.
+
+An in-flow child of a flex container is positioned by flex and must omit
+`x`/`y`. `flow="absolute"` removes it from flex participation and restores
+its bindings against the container's local box. `flow` is valid only on a
+child of a flex container; `grow` and `align` are valid only while that child
+remains in flow.
+
+### Size constraints and aspect ratio
+
+Minimum and maximum constraints apply to `container`, `rect`, `ellipse`,
+`line`, `path`, and `text`. They are invalid on `group` and `lens`, whose boxes
+are derived. Resolution first obtains an axis from its binding, declared
+intent, measurement, or layout, then clamps it to the applicable maximum and
+minimum.
+The minimum wins when a minimum exceeds its paired maximum. If constraining a
+text width changes its wrapping width, text is measured again before its final
+height is chosen. Because a line's height is definitionally zero,
+`min-height` and `max-height` are invalid on ``.
+
+In Draft 0, `aspect-ratio="w:h"` applies only to `rect`, `ellipse`, and `path`.
+It contains two positive numbers and supplies exactly one otherwise
+under-specified axis from the other. It never distorts two extents that are
+already determined and never invents a scale transform. Using an aspect ratio
+where neither axis can provide the other, or redundantly declaring it when
+both axes are already supplied, is an error.
+
+### Rounded box geometry
+
+`container` and `rect` accept `corner-radius` and `corner-smoothing`. Both
+properties modify the node's original rectangular outline; they do not create
+a second shape, resize the layout box, or change the rectangular coordinate
+space used by its paints.
+
+`corner-radius` contains one or exactly four non-negative finite logical-pixel
+values. One value applies to every corner. Four values are ordered top-left,
+top-right, bottom-right, bottom-left. An optional slash followed by an
+independent one-or-four-value list makes the radii elliptical: values before
+the slash are horizontal radii (`rx`), and values after it are vertical radii
+(`ry`). Without a slash, each vertical radius equals its corresponding
+horizontal radius.
+
+```xml
+
+
+
+```
+
+Lists of two or three values are invalid; Draft 0 deliberately avoids the
+positional expansion rules of CSS shorthand. A slash must have a valid list on
+both sides and may appear only once. A canonical writer separates values with
+one ASCII space and spells the separator as `/`. The default is four zero
+radius pairs. If either effective component of a pair is zero, that corner is
+square.
+
+Corner names refer to the node's untransformed local box. Native flips,
+rotation, and ancestor transforms apply to the completed outline afterward;
+they never rewrite or permute the authored top-left, top-right, bottom-right,
+and bottom-left values.
+
+For an ordinary rounded outline (`corner-smoothing="0"`), overlapping authored
+radii resolve with one proportional scale factor for all eight radius
+components. Let `W` and `H` be the resolved width and height. The factor is the
+minimum of `1` and each applicable edge ratio:
+
+- `W / (rx_top-left + rx_top-right)`;
+- `W / (rx_bottom-left + rx_bottom-right)`;
+- `H / (ry_top-left + ry_bottom-left)`;
+- `H / (ry_top-right + ry_bottom-right)`.
+
+An edge ratio whose radius sum is zero imposes no constraint. Multiplying all
+radii by this one factor prevents overlap while preserving their relative
+proportions. Normalization is a resolved geometry operation: it never rewrites
+the authored `corner-radius` value.
+
+`corner-smoothing` is a finite scalar in `[0, 1]`; `0` is the ordinary
+elliptical rounded-rectangle outline. Nonzero values request progressively
+smoothed corners. The current production smoothing renderer is circular-only:
+it collapses each elliptical `(rx, ry)` pair to a circle. Draft 0 therefore
+rejects a nonzero `corner-smoothing` when any corner has unequal horizontal and
+vertical radii, rather than silently changing authored geometry. Smoothed
+elliptical corners remain unavailable pending lossless renderer support.
+Nonzero smoothing with four zero radius pairs is valid dormant intent and
+remains serializable even though it has no visible effect.
+
+For a nonzero smoothing value, Draft 0 follows the existing production
+smoothed-box profile instead: each circular radius is independently capped to
+half the box's shorter side before constructing its curve. Unlike ordinary
+edge-sum normalization, that cap may change a lone large corner and does not
+rescale the other authored corners. It is resolved geometry, never a source
+rewrite. Keeping this difference explicit projects the current Grida model and
+renderer without pretending the ordinary and continuous-corner algorithms are
+the same operation.
+
+The same resolved outline is the source for a node's fill, every repeated
+stroke, and a container's descendant clip when `clips="true"`. Each stroke is
+still derived independently from that original outline. Paint coordinates
+continue to use the full rectangular node box, including transparent space
+outside the rounded coverage; rounding or smoothing never shrinks or warps the
+paint box.
+
+## Box semantics by element
+
+### Container
+
+A container is a rectangular composition box. Each of `width` and `height`
+accepts either a non-negative number or `auto`. On an auto axis, the local
+start edge remains fixed and the extent grows through the greatest positive
+child far edge plus padding; auto sizing never shifts the local origin.
+Consequently, a child with a sufficiently negative start offset may overflow
+the start side rather than moving the container. A fixed value remains
+authored intent even when a parent layout gives the node a different resolved
+extent. Both dimensions default to `auto`.
+
+Without `layout="flex"`, children are free-positioned by their bindings. On
+an auto axis, free children must be start-bound; an end, center, or span
+binding would require the extent it is trying to determine and is therefore a
+resolution error. Their bindings resolve against the padding-inset content
+box, then add the container's left and top padding; therefore `x="0"` and
+`y="0"` start at the padded content origin.
+
+A container may carry a fill and repeated strokes and may clip descendant
+paint with `clips="true"`. Its fill is painted first. Its children are then
+painted under the descendant clip, if enabled; that clip uses the container's
+resolved rounded or smoothed outline. After the descendant clip is removed,
+the container's strokes are painted last. The default container fill is empty
+and the default stroke list is empty.
+
+### Primitive elements
+
+Draft 0 recognizes the direct primitive elements `rect`, `ellipse`, `line`,
+and `path`. They do not accept a `kind` attribute.
+
+- `rect`, `ellipse`, and `path` require each axis to be supplied by either a
+ fixed numeric size or a `span` binding. `aspect-ratio` may supply exactly
+ one otherwise-unsupplied axis.
+- `rect` may round and smooth its rectangular outline. These properties affect
+ its fill and strokes, but do not implicitly clip its children.
+- `line` requires either fixed numeric `width` or an x-axis `span`. Its
+ authored and resolved height is zero, so `height` and a y-axis `span` are
+ invalid. It is the horizontal segment from local `(0, 0)` to `(width, 0)`
+ and can be oriented with `rotation`. It has no fill; each authored `stroke`
+ independently strokes that segment. With no strokes it contributes no
+ pixels.
+- An unresolved `auto` axis is invalid on a primitive because these elements
+ have no content-based natural size. `aspect-ratio` is invalid on `line`.
+
+An ellipse whose width equals its height is a circle. A `circle` kind or
+element is invalid: it would duplicate rather than extend the primitive
+model.
+
+The primitive and its children share the primitive's local box, but are
+otherwise independent. Changing a child does not rewrite the primitive, and
+resizing the primitive does not rewrite a child's authored bindings.
+
+#### Path geometry
+
+`path` is a declared-box shape, not intrinsically measured SVG content. Its
+required `d` attribute uses the complete SVG path-data grammar: absolute and
+relative commands, lines, cubic and quadratic curves, elliptical arcs,
+multiple contours, and close-path commands are all available. The grammar is
+borrowed; SVG's surrounding viewport model is not.
+
+Path commands are authored in one fixed reference rectangle: `0 0 1 1`. The
+exact tight geometry of every drawable contour must remain within that closed
+unit rectangle. Validation uses the realized curve extrema, not merely
+endpoints or control-point coordinates. A curve whose control point leaves the
+unit rectangle is therefore valid if the curve itself stays inside; a curve
+that overshoots is invalid even when every endpoint is inside.
+
+For a resolved box of width `W` and height `H`, each reference-space point
+`(u, v)` realizes as `(u × W, v × H)`. The two axes scale independently. This
+geometry mapping happens before fill and stroke coverage are constructed, so
+layout resize changes the outline without rewriting `d`, while stroke widths,
+dash lengths, and other style lengths remain logical-pixel values. The same
+rule applies when span bindings, aspect-ratio resolution, flex growth, or
+cross-axis stretching determine the final box.
+
+The fill rule is `nonzero` by default and may be changed with
+`fill-rule="evenodd"`. Omitted fill produces the ordinary primitive default of
+one opaque black solid. Repeated strokes use the same realized outline and may
+use scalar width, cap, join, miter limit, and dash array. A path stroke defaults
+to center alignment. Inside or outside alignment requires every contour to be
+closed; a path containing any open contour must use center. Per-side stroke
+width is invalid because a path has no four box sides to which those widths
+could attach.
+
+Every path paint, including gradients and images, evaluates against the full
+resolved rectangular box rather than the path's tight ink bounds; fill and
+stroke coverage still follows the realized outline. The path does not
+establish a descendant clip. Children use the same resolved box-local
+coordinate space as children of `rect` and `ellipse`; they do not inherit the
+unit path coordinate space. The realized path's tight geometry seeds visual
+bounds, effective strokes are included conservatively, and damage covers the
+result, while the declared box remains the path's layout contribution. Hit
+testing and editor selection policy are consumer concerns; Grida XML defines
+no separate authored hit region.
+
+```xml
+
+
+
+
+ A
+
+```
+
+#### Considered path coordinate models
+
+The fixed unit rectangle is accepted because it keeps path geometry on the
+same side of the layout boundary as every other primitive: the box negotiates
+with layout, and the path is a pure function of that box. Resize never rewrites
+the command stream, `x` and `y` always place the box's top-left origin, and a
+small edit to one curve cannot silently redefine the coordinate system for
+all other contours.
+
+Raw intrinsic coordinates with tight-bounds measurement are rejected as the
+canonical model. [SVG path data](https://www.w3.org/TR/SVG2/paths.html#PathData)
+uses the current user coordinate system, while
+[`viewBox`](https://www.w3.org/TR/SVG2/coords.html#ViewBoxAttribute) defines a
+mapping on a viewport; a raw path has no intrinsic viewport of its own.
+Treating its tight bounds as a Grida box
+would make path edits change layout contribution, permit a nonzero content
+origin unlike other boxed primitives, and leave flex-assigned sizes with no
+defined relationship to the painted outline. Tight-fitting raw geometry into
+the assigned box is also rejected: intentional padding disappears, zero-width
+or zero-height geometry has no scale, and changing one curve extremum remaps
+every untouched contour. Those failures are structural, not parser details.
+
+An optional `view-box="min-x min-y width height"` is a plausible later
+ergonomic extension for lossless copying of arbitrary SVG coordinate values.
+It is deferred because the unit form already expresses the geometry without a
+second reference rectangle, and admitting it requires stable-reference,
+positive-extent, validation, and canonical-writer rules. If introduced, the
+reference rectangle must be authored intent and must never be continuously
+re-derived from tight bounds. `view-box` is unknown syntax in Draft 0.
+
+#### Lessons retained
+
+- The declared box is the sole surface that negotiates with layout; path
+ geometry realizes into it and never becomes a competing measurement.
+- A reference space is source intent. Whether the fixed unit rectangle or a
+ future explicit rectangle, it must not be recomputed from changing tight
+ bounds.
+- Geometry maps before coverage is constructed, so fills retain their
+ box-relative coordinates and stroke widths and dash lengths remain
+ pixel-stable.
+- One resolved path artifact must supply painting, tight geometry bounds, and
+ damage. Independent reparsing or geometry reconstruction may not produce
+ divergent answers.
+- Reference-to-box mapping is a resolution operation and happens once in the
+ resolver's numeric domain. Visual bounds must conservatively enclose that
+ exact mapped command stream; independently scaling source-space bounds is
+ not equivalent at finite precision.
+- A numeric environment that cannot represent a valid mapped command stream
+ must report a resolution failure. It must not clamp controls, retain
+ non-finite geometry, substitute box bounds, or paint only a surviving subset.
+- Source equality and visual equality are different contracts. The source tier
+ retains authored `d`; a resolved visual comparison may ignore relative versus
+ absolute spelling only after both forms produce the same command stream,
+ fill rule, closure state, and conservative bounds.
+- Raw intrinsic paths and box-mapped paths are different models. A boundary
+ must convert explicitly or reject; it must not relabel one as the other.
+- Fill rule is part of path material identity. It survives parsing,
+ resolution, rendering, inspection, and rewriting with the path geometry.
+
+### Text
+
+`text` is the scene node. It owns the text box, paragraph properties, default
+text style, node fill stack, and repeated node stroke geometries.
+
+The [Universal Shaped Text Layout](../feat-paragraph/text-layout) RFD owns the
+shaping, font resolution, line construction, UTF-8 mapping, metrics, and
+resolved bounds produced from that source. This section defines only the XML
+text intent and its mapping into that contract. Grida XML never serializes the
+resolved text-layout artifact.
+
+Its content is one flat sequence of direct character data and contextual
+`tspan` runs:
+
+```text
+text := node-fill? stroke* segment*
+segment := character-data | tspan
+tspan := run-fill? non-empty character-data
+node-fill := fill
+run-fill := fill
+```
+
+There may be at most one leading `fill`; `stroke` is repeatable. Once the first
+character-data segment or `tspan` begins, no node property may follow. A
+`tspan` must be a direct child of `text`, cannot itself contain `tspan`, and may
+contain no element other than its optional singular leading `fill`. It is not
+a scene node: `id`, `name`, `x`, `y`, `width`, `height`, `rotation`, `opacity`,
+layout attributes, render children, and SVG chunk-positioning attributes such
+as `dx`, `dy`, and per-chunk `rotate` are invalid on it.
+
+Whitespace is exact character content, not formatting, once content begins.
+Formatting whitespace before and between leading node properties is ignored.
+After the last leading property closes—or immediately after the opening tag
+when there is no property—every direct character is content, including spaces,
+tabs, and newlines before, between, or after `tspan` children. Every character
+inside `tspan` is likewise content. XML entity references are decoded before
+the string and ranges are formed. XML comments contribute no characters. An
+empty `tspan` is invalid after XML decoding; a whitespace-only `tspan` is
+non-empty and significant. This means pretty indentation inside mixed text
+changes the design, so a canonical writer keeps intended content adjacent to
+its tags:
+
+```xml
+Ship boldly.
+```
+
+`text` establishes a complete default run style. Draft 0 exposes only these
+typographic attributes on `text` and `tspan`:
+
+| Attribute | Value | `text` default | `tspan` behavior |
+| ------------- | ------------------------------- | -------------- | -------------------------------- |
+| `font-size` | positive finite logical pixels | `16` | overrides the default run size |
+| `font-weight` | integer from `1` through `1000` | `400` | overrides the default run weight |
+| `font-style` | `normal` or `italic` | `normal` | overrides the default run style |
+
+An omitted `tspan` attribute inherits from `text`, never from the preceding
+run or sibling. The `size` spelling is invalid; `font-size` is the one
+canonical spelling. Draft 0 leaves font-family selection and every unexposed
+production text-style field to the declared resolution environment and its
+defaults. A resolved artifact records the shaping result without writing it
+back into source. A canonical writer omits all three default values on `text`.
+
+There is no `style` mini-language. `` is not an alias for `tspan`, and
+HTML semantic or presentational elements such as `b`, `strong`, `i`, `em`, and
+`small` are invalid. The production attributed-string model has no matching
+semantic or accessibility annotation to preserve, so inferring meaning from
+such tags would be lossy. Authors express the supported visual fact directly,
+for example `font-weight="700"` or `font-style="italic"`. `tspan` borrows the
+familiar name, not SVG's independently positioned text-chunk model.
+
+#### Considered rich-text syntax
+
+1. **`text` with direct, flat `tspan` runs — accepted.** It projects the flat
+ production attributed string and keeps run overrides explicit.
+2. **HTML semantic or presentational tags — rejected as canonical.** A separate
+ import dialect may deliberately lower them to visual run properties, but
+ Grida XML cannot preserve semantics the production model does not own and a
+ canonical writer never emits them.
+3. **Nested `tspan` — rejected in Draft 0.** Production runs are flat byte
+ ranges, and nested source boundaries cannot be reconstructed after adjacent
+ equivalent runs merge. A later version would need additional preserved
+ structure rather than pretending the nesting survived.
+4. **`style="…"` — rejected.** A CSS-like declaration string would create a
+ second nested grammar with weaker validation and diagnostics than explicit
+ kebab-case attributes.
+
+#### Attributed-string materialization
+
+Materialization concatenates direct `text` character data and each `tspan`'s
+character data in document order into one UTF-8 backing string. Authors never
+spell run offsets. For each non-empty segment, the materializer derives an
+inclusive start and exclusive end as UTF-8 byte offsets at character
+boundaries. Direct character data receives the complete `text` default style;
+`tspan` data receives that same complete style with its explicit overrides.
+The resulting runs are ordered, contiguous, non-overlapping, and cover the
+entire backing string. Adjacent runs are merged only when their complete style
+and run-paint override state are identical. An empty `text` materializes to the
+production empty-string special case with one default `0..0` run.
+
+Node `fill` remains the ordered fallback paint stack for the whole text node.
+Direct text and a `tspan` without `fill` materialize with
+`StyledTextRun.fills = None`, which means node-fill fallback. `fill="#RRGGBB"`
+on `tspan` materializes as `Some([solid])`, preserving an explicit single-solid
+override even when its pixels currently match the node fill. This is the
+compact per-run fill spelling.
+
+A `tspan` may instead begin with one singular `` property containing the
+same ordered typed `Paints` as a node fill. It must be the literal first child:
+no character data, comment, or formatting whitespace may precede it. Once it
+closes, every character is content. The `fill` attribute and property are
+mutually exclusive, and no `fill` may follow character content. Omission maps
+to `fills = None`, an empty `` maps to `Some([])` and therefore explicit
+no ink, and a non-empty property maps to `Some(ordered paints)`. The canonical
+writer uses the `fill` attribute for one ordinary solid override and uses the
+property for explicit emptiness or every other stack, exactly matching the
+node-fill partition:
+
+```xml
+A gradient runmasked
+```
+
+The current packed `.grida` encoder/decoder collapses an empty run-fill vector
+to an absent one. That persistence boundary cannot yet round-trip ``:
+restoring the node fill would change explicit no ink into visible ink. A
+converter must reject that state or declare a non-round-tripping subset until
+the archive preserves presence independently from vector length; it must not
+silently normalize `Some([])` to `None`.
+
+Structured run-fill gradient and image coordinates resolve against the
+resolved full text-node paint box, exactly as node fills do. They do not
+restart in each `tspan` fragment or use a fragment's glyph bounds. The current
+production attributed renderer passes `(width, width)` as the paint box for
+run fills and strokes; using width for height is an implementation
+incompatibility to fix against the resolved text-node width and height, not a
+distinct XML coordinate system.
+
+Node-level repeated strokes remain valid on `text` and apply to its shaped
+glyph contours. Draft 0 defines no `tspan` stroke syntax. Production
+`StyledTextRun` has only one optional stroke `Paints` stack and one optional
+width/alignment geometry, while Grida XML stroke topology permits repeated,
+independent geometries. Those models cannot losslessly project each other, so
+a writer must reject a production run-stroke override and the language must
+defer run strokes until that multiplicity seam is resolved.
+
+Text is a box, not a point label:
+
+| Width intent | Height intent | Behavior |
+| ------------ | ------------- | -------------------------------------------------------- |
+| `auto` | `auto` | Measure natural lines; do not introduce soft wrapping |
+| fixed | `auto` | Wrap to the fixed width and measure the resulting height |
+| fixed | fixed | Wrap to the fixed width inside the fixed-height text box |
+| `auto` | fixed | Measure natural width inside the fixed-height text box |
+
+Both attributes default to `auto`. A span-resolved width is a fixed wrapping
+constraint. Text wraps at the legal break opportunities provided by the
+declared text-resolution environment. Explicit line breaks are preserved in
+every mode. Draft 0 does not define truncation or ellipsis; fixed height
+constrains the box but does not silently alter the text content. Text paint may
+overflow that box unless an ancestor container clips it.
+
+### Group
+
+A group has no declared width or height and no visual content of its own. Its
+box is the union of its children's untransformed local layout boxes. Child
+rotation, flips, and lens operations affect visual bounds but do not enlarge
+that sizing-tier union. The group's `x` and `y` place the group's local origin,
+not the top-left of that union; consequently, a child may extend into negative
+local coordinates without moving its siblings.
+
+### Lens
+
+A lens has the same derived-box and origin rules as a group. Its `ops`
+attribute is an ordered, space-separated list drawn from:
+
+```text
+translate(x,y)
+rotate(degrees)
+scale(s)
+scale(x,y)
+skew-x(degrees)
+skew-y(degrees)
+skew(x-degrees,y-degrees)
+matrix(a,b,c,d,e,f)
+```
+
+Operations compose in source order and affect descendant painting and
+resolved visual bounds. They do not change flex contribution, hug sizing, or
+sibling placement. A lens is therefore explicit picture-transform intent,
+not a place to store a matrix produced by layout.
+
+## Flex layout
+
+Only `container` may own flex layout in Draft 0. Its `layout` value is `none`
+or `flex`, defaulting to `none`; a canonical writer omits the default. Nesting
+children under a primitive element, group, or lens remains free placement;
+those elements do not acquire layout behavior merely because they can contain
+children.
+
+`layout="flex"` enables a one- or multi-line, CSS-inspired flow with this
+surface:
+
+| Attribute | Values | Default |
+| ----------- | ------------------------------------------------------------------------- | ------- |
+| `direction` | `row`, `column` | `row` |
+| `wrap` | `true`, `false` | `false` |
+| `gap` | one non-negative number, or main/cross numbers | `0` |
+| `padding` | one non-negative number, or top/right/bottom/left numbers | `0` |
+| `main` | `start`, `center`, `end`, `space-between`, `space-around`, `space-evenly` | `start` |
+| `cross` | `start`, `center`, `end`, `stretch` | `start` |
+
+`padding` applies to both free-positioned and flex containers. The other
+attributes in this table are valid only when `layout="flex"` is present.
+
+In-flow children participate in source order. Their resolved size is their
+flex basis. Positive remaining main-axis space is divided in proportion to
+`grow`; Draft 0 does not shrink children. `main` distributes remaining space,
+while `cross` and a child's `align` place or stretch it on the cross axis.
+Container-level `cross="stretch"` stretches only children whose authored
+cross size is `auto`; a fixed cross size remains fixed. Child-level
+`align="stretch"` is an explicit fill override and stretches even a fixed
+cross size. Wrapping forms additional lines when enabled. An absolute child
+does not consume a flex slot, gap, or growth share.
+
+Layout owns resolved placement, never source geometry. Re-resolving at a new
+container extent may move or resize children without writing those results
+into the document.
+
+## Canonical example
+
+This slide exercises flex layout, constrained text, an ellipse used as a
+circle, and text nested directly under a primitive element:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+ 01
+
+ Design responsive 2D content that wraps predictably.
+
+
+```
+
+Formatting and attribute order are not semantic. The element names,
+`version`, `width`/`height` spellings, value grammars, and child order are.
+
+## Source intent and resolved output
+
+The `.grida.xml` document is the authored-intent tier. It stores the facts an
+author chose: node kinds, hierarchy, bindings, size intent, text, paint,
+layout relationships, and explicit lens operations.
+
+A resolver combines that source with an explicit environment—viewport,
+fonts, and resources—to produce a separate resolved scene. Text shaping and
+geometry follow the single-result contract in [Universal Shaped Text
+Layout](../feat-paragraph/text-layout). Resolved boxes, world transforms,
+measured glyph runs, visual bounds, materialized vector points, and paint
+commands belong to that derived output. They are not fields of Grida XML
+merely because a renderer can compute them.
+
+This separation has three consequences:
+
+1. Reading or rendering a file must not rewrite responsive intent into the
+ values observed in one environment.
+2. A canonical writer serializes source intent, not a resolved snapshot.
+3. Tools may expose a materialized view for inspection or editing, but any
+ source edit must deliberately retarget the appropriate intent rather than
+ copying every resolved value back into the document.
+
+## Strict parsing and versioning
+
+Draft 0 uses strict current-version parsing. A reader either produces the
+declared Draft 0 scene or reports a typed failure. It must reject:
+
+- malformed XML;
+- CDATA sections, document types, or processing instructions;
+- an envelope other than `grida`;
+- a missing version or any version other than exactly `0`;
+- no render root, more than one render root, or a root other than `container`;
+- `flow`, `grow`, or `align` on the render root;
+- unknown elements, attributes, enum values, or lens operations;
+- the reserved `` element or `kind` on a render node;
+- attributes used on a node kind where they do not apply;
+- a typed paint outside `fill` or `stroke`, a `stop` outside `gradient`, a
+ nested `fill` or `stroke`, a duplicate `fill`, or a structural paint child
+ placed after text content, a `tspan`, or render children;
+- simultaneous use of the `fill` attribute and `fill` element on one node;
+- unknown paint tags or paint attributes, a generic `paint` envelope, an
+ invalid paint blend mode, or a non-boolean paint visibility value;
+- a gradient with a missing or unknown `kind`, an attribute not valid for its
+ kind, fewer than two stops, a stop outside `[0,1]`, descending stop offsets,
+ a non-invertible gradient affine, or linear endpoints at or below the
+ declared unit-space degeneracy threshold;
+- an empty image `src` or unsupported image fit;
+- a fill on `line`, any fill or stroke on `group` or `lens`, an empty stroke
+ whose geometry equals its target defaults, a stroke attribute invalid for its
+ target, invalid stroke geometry, a non-centered line stroke, or a malformed
+ dash array;
+- a stroke width with neither one nor four values, a negative or non-finite
+ side, four values on a non-box target, or a per-side width combined with
+ nonzero corner smoothing, a non-miter join, or a non-default miter limit;
+- a `path` without `d`, malformed SVG path data, path geometry whose exact
+ tight bounds leave the closed unit rectangle, an unknown `fill-rule`, a
+ four-value path stroke width, or non-center stroke alignment on a path with
+ any open contour;
+- non-finite numbers, negative dimensions, or malformed colors;
+- malformed `corner-radius` lists, negative radii, `corner-smoothing` outside
+ `[0, 1]`, either corner property on an inapplicable element, or nonzero
+ smoothing paired with any elliptical corner;
+- a `tspan` outside `text`, a nested or empty `tspan`, a render element inside
+ `text`, or any element other than one leading `fill` inside `tspan`;
+- simultaneous `fill` attribute and child on `tspan`, duplicate run `fill`, or
+ a run `fill` placed after any character data or comment;
+- ``, HTML text tags, a `style` attribute, `size`, or SVG chunk-position
+ attributes on `tspan`;
+- non-positive font sizes, non-integer or out-of-range font weights, unknown
+ font styles, non-positive aspect-ratio terms, negative growth, gap, or
+ padding values, or opacity outside `[0, 1]`;
+- non-whitespace character data inside an element other than `text` or
+ `tspan`;
+- contradictory geometry such as a fixed width alongside an x-axis span.
+
+Unsupported future versions must fail as unsupported versions. A Draft 0
+reader must not attempt best-effort interpretation, preserve unknown semantic
+fields as inert bags, or drop unknown nodes. Such tolerance would make a file
+appear valid while changing its design.
+
+Diagnostics should identify the offending element and attribute or child,
+state the expected grammar, and distinguish syntax, semantic validation, and
+resource resolution. For example, a misplaced image should say that image
+paints belong directly under `fill` or `stroke`; a gradient without `kind`
+should list the four accepted values; a descending stop should identify the
+two offsets; a malformed corner-radius should identify the failing side of the
+slash and require one or four values; a malformed per-side width should name
+the failing top, right, bottom, or left value; a smoothing or join conflict
+should identify the per-side stroke it cannot represent; an unsupported second
+stroke should identify the target model's single-geometry limit; and a missing
+image file should report its authored `src` rather than presenting the failure
+as malformed XML. Useful width diagnostics include `stroke width takes 1 or
+exactly 4 numbers in top right bottom left order; got 3`, `stroke width right
+must be a non-negative finite number`, and `four-value stroke width is valid
+only on and `. Useful text diagnostics include `empty
+is invalid; put empty text on or remove the run`, `nested is
+invalid; runs must be flat direct children of `, ` inside
+must be the literal first child; no whitespace may precede it`, and `size is
+not Grida XML; use font-size`. Useful path diagnostics identify the failing
+command or number and distinguish grammar from geometry, for example `path d:
+invalid arc flag at byte 19`, `path geometry exceeds the unit box on the right:
+max-x is 1.08`, and `outside path stroke requires every contour to be closed`.
+
+## Conformance
+
+The normative terms in this section define Draft 0 conformance.
+
+### Reader
+
+A conforming reader:
+
+1. **MUST** enforce the document envelope, version, single-container root,
+ vocabulary, value grammar, attribute applicability, and child rules in
+ this RFD.
+2. **MUST** preserve node order, text content, and authored intent exactly at
+ the semantic level, including stroke order, paint order, inactive paints,
+ equal-offset stops, empty fill stacks where omission changes semantics,
+ per-character resolved text style and run-fill override state, and empty
+ strokes with non-default geometry. It must preserve path commands,
+ contour closure, and fill rule without replacing them with tight-fit output.
+3. **MUST NOT** accept an unknown construct by ignoring or coercing it.
+4. **MUST** distinguish a parse failure from a resolution failure. A
+ well-formed intent document can still fail to resolve in an insufficient
+ environment.
+
+### Writer
+
+A conforming writer:
+
+1. **MUST** emit `grida version="0"` with exactly one `container` child.
+2. **MUST** emit the canonical Draft 0 node and attribute names, including
+ `container`, `rect`, `ellipse`, `line`, `path`, `tspan`, `width`, `height`,
+ `d`, and `font-size` rather than historical aliases.
+3. **MUST** apply the canonical fill partition: omit the default, use the
+ `fill` attribute for one ordinary solid, and use the `fill` element for all
+ other non-empty stacks or required explicit emptiness. The same partition
+ applies to an explicit `tspan` run-fill override.
+4. **MUST** normalize a default empty stroke pair to omission, preserve an
+ empty stroke with non-default geometry, and emit every remaining stroke in
+ bottom-to-top order with its typed paints in bottom-to-top order.
+5. **MUST** normalize equal per-side widths to the uniform or no-width state,
+ omit uniform width `1`, emit other uniform values as one number, and emit
+ exactly four top-right-bottom-left values only for unequal rectangular
+ widths.
+6. **MUST** emit a gradient's required `kind`; it **MUST NOT** emit the four
+ kind-specific gradient element names, ``, or a generic `paint`
+ envelope.
+7. **MUST** encode XML text and attribute values correctly and emit only
+ finite, valid values. It must emit path data in the authored unit reference
+ space without tight-fitting it to observed ink, omit the default
+ `fill-rule="nonzero"`, and emit `fill-rule="evenodd"` when selected.
+8. **MUST** derive text run boundaries from the one backing UTF-8 string, merge
+ adjacent runs with identical complete style and paint-override state, emit
+ no default-valued typography attributes on `text`, emit
+ direct character data for each maximal run equal to the `text` default with
+ no run-fill override, and emit one flat `tspan` for every other maximal run.
+ A `tspan` carries only typographic attributes that differ from the `text`
+ default plus its fill override; it is never nested or empty, and authored
+ byte offsets are never emitted.
+9. **MUST NOT** pretty-indent mixed text content, because inserted spaces or
+ newlines are semantic. It must also reject an unsupported production run
+ style or run stroke rather than silently dropping it.
+10. **MUST** emit the shortest exact one-or-four-value representation for each
+ corner-radius axis, omit the slash when every `rx` equals its `ry`, and
+ omit each corner attribute when its value is the zero default.
+11. **MUST NOT** serialize resolved-only values, including normalized effective
+ corner radii or materialized path points, as if they were source intent.
+12. **SHOULD** produce stable, human-readable indentation outside mixed text
+ content and stable attribute ordering, although neither is semantic in
+ Draft 0.
+
+### Resolver and renderer
+
+A conforming resolver and renderer:
+
+1. **MUST** resolve child coordinates in their parent's local space,
+ including children of primitive elements.
+2. **MUST** apply free bindings, text measurement, auto sizing, and flex
+ ownership according to this RFD, including size constraints and aspect
+ ratios.
+3. **MUST** map path geometry from the fixed unit rectangle nonuniformly into
+ the final resolved box before constructing fill or stroke coverage. It must
+ preserve logical-pixel stroke and dash lengths, apply the declared fill
+ rule, seed visual bounds from realized tight geometry, conservatively
+ include effective strokes, and cover those bounds in damage. Mapping,
+ bounds, fill, and every stroke must consume one resolved command artifact;
+ an implementation must not independently rescale source bounds or reparse
+ `d`. If its numeric domain cannot represent that artifact, resolution must
+ fail explicitly without partial path ink.
+4. **MUST** preserve source order in painting, with the node's fill first,
+ children in document order, and repeated parent strokes from first to last.
+5. **MUST** derive each stroke independently from the node's original outline
+ and composite that stroke's visible paints in bottom-to-top order. It must
+ not merge distinct stroke geometries into one paint stack.
+6. **MUST** resolve rectangular stroke widths as one outer-minus-inner ring
+ using the declared local-side widths and alignment fractions. It must treat
+ an overconsumed inner box as an empty contour, include each side's actual
+ outward extent in visual bounds, and leave the layout box unchanged.
+7. **MUST** use one proportional edge-sum factor for ordinary rounded-box
+ overlap normalization and the per-corner half-short-side cap for nonzero
+ smoothing. It must then use the same resulting outline as the source for
+ fill coverage, every stroke, and a container's descendant clip. The
+ rectangular paint box must remain unchanged.
+8. **MUST** composite every visible paint in its declared bottom-to-top order,
+ applying each paint's opacity and blend mode to that paint only.
+9. **MUST** evaluate gradient geometry in the declared unit paint space and
+ map it to the resolved paint box without rewriting source coordinates.
+10. **MUST** resolve image identifiers through the declared resource
+ environment and surface missing or undecodable resources as resolution
+ failures rather than silently dropping their paint layers.
+11. **MUST** shape `text` and its flat `tspan` segments as one backing string,
+ use the node fill when a run has no override, and preserve explicit empty
+ and ordered run-fill stacks. Every node and run fill uses the resolved full
+ text-node paint box; paint coordinates do not restart per run.
+12. **MUST** include every effective stroke in resolved visual bounds without
+ changing the node's layout box.
+13. **MUST** apply rotation and native flips as visual-only transforms and
+ apply clipping and opacity to the declared subtree.
+14. **MUST NOT** mutate the source document as a side effect of resolving or
+ rendering it.
+15. **MUST** make the resolution environment explicit enough that a resolved
+ result can be attributed to a viewport, font set, and resource set.
+
+A processor that intentionally supports only part of Draft 0 may describe
+itself as a Draft 0 subset, but it cannot claim full Draft 0 conformance and
+must not silently discard the unsupported remainder.
+
+## Deferred requirements
+
+Draft 0 does not define editable vector networks, non-unit path reference
+rectangles, variable-width strokes, dash offset, endpoint markers, effects,
+scene image nodes, resource declaration or packaging syntax, advanced image
+placement, image filters, rich color spaces, advanced typography, per-run
+strokes, semantic text annotations, grid layout, animation, or durable node
+identity. The later [durable-addressing RFD](./grida-xml-addressing) accepts
+required render/use identity for exact Version 4 while leaving Draft 0
+unchanged. Every other eventual addition must preserve the one-tree,
+local-space, intent-only model established here.
+
+Reusable paint definitions and references are also deferred. Inline paint
+values remain sufficient and canonical; a later reference system must define
+identity, scope, failure behavior, and override semantics without creating a
+second canonical spelling for the same inline value.
+
+Reusable author-defined widgets/components are a required future capability.
+The [Grida XML modules and static component reuse](./grida-xml-modules) RFD
+proposes top-level boxed definitions and self-contained
+`` references, with source linking and materialization kept
+above ordinary scene resolution. It also records identity, lexical resource
+origin, cycles, and source-writing requirements. The [Grida XML component
+parameters](./grida-xml-component-parameters) RFD separately proposes typed
+scalar `prop` declarations, explicit `arg` children, and exact bindings for
+Version 2. The [Grida XML component slots](./grida-xml-component-slots) RFD
+proposes Version 3 empty named `` declarations and direct render roots
+under `use` carrying the contextual `slot` assignment relationship.
+
+Those proposals remain later-version contracts. Draft 0 defines neither
+`component`, `use`, `prop`, `arg`, nor any component binding syntax, and a
+current-version reader must reject them rather than interpreting the proposed
+syntax early. Draft 0 also grants no meaning to the Version 3 ``
+declaration or contextual `slot` assignment attribute, nor to invented
+`content` or `children` elements. Brace characters remain ordinary Draft 0
+character data or attribute characters wherever that position's existing
+value grammar admits them; Draft 0 never interprets braces as bindings.
+
+The reserved `shape` name may support custom-shape definitions in a later
+version, but Draft 0 does not decide whether it names a definition or an
+instance, where definitions live, how geometry is expressed, or how reuse
+works. Adding any such behavior is a versioned language change.
+
+## Open questions beyond Draft 0
+
+- Which advanced stroke geometries and effects are small enough to become
+ stable source vocabulary without weakening the typed-paint model?
+- How are fonts and external image resources declared or packaged so that
+ resolution remains portable while relative identifiers remain authorable?
+- What durable member identity and selector grammar should address paints,
+ strokes, gradient stops, text runs, vector members, and lens operations
+ without using unstable list indexes?
+- Which resolved inspection form should accompany the intent source without
+ being mistaken for it?
+- Does the compound `.grida.xml` suffix remain the best long-term authored
+ file identity?
diff --git a/docs/wg/format/index.md b/docs/wg/format/index.md
index 3445321295..d865655243 100644
--- a/docs/wg/format/index.md
+++ b/docs/wg/format/index.md
@@ -1,5 +1,6 @@
---
title: Format & Import Mapping
+description: Specifications for Grida's authored formats and trackers for importing external formats into the Grida IR.
format: md
tags:
- internal
@@ -11,26 +12,40 @@ tags:
Tracking docs for the Grida IR schema and how external formats map into it.
-## Pages
+## Specifications and RFDs
-| Page | Description |
-| ------------------- | -------------------------------------------------- |
-| [Grida IR](./grida) | Canonical IR reference — node types, paint, layout |
-| [CSS](./css) | CSS → Grida IR property mapping and TODO tracker |
-| [HTML](./html) | HTML element → Grida IR node mapping |
-| [SVG](./svg) | SVG → usvg → Grida IR mapping and TODO tracker |
+| Page | Description |
+| ------------------------------------------------------------------ | ------------------------------------------------------------ |
+| [Grida IR](./grida) | Canonical IR reference — node types, paint, layout |
+| [Grida XML](./grida-xml) | Open RFD for the authored, inspectable `.grida.xml` source |
+| [Grida XML properties](./grida-xml-properties) | XML property names, applicability, and design placeholders |
+| [Grida XML modules](./grida-xml-modules) | Open linking/component RFD with a proving implementation |
+| [Grida XML component parameters](./grida-xml-component-parameters) | Open typed prop/arg RFD with a proving implementation |
+| [Grida XML component slots](./grida-xml-component-slots) | Open named slot projection RFD with a proving implementation |
+| [Grida XML durable addressing](./grida-xml-addressing) | Version 4 node/use identity and typed effective-value RFD |
+| [Grida XML animation](./grida-xml-animation) | Open day-one animation question set for `.grida.xml` |
+
+## Import mappings
+
+| Page | Description |
+| -------------- | ------------------------------------------------ |
+| [CSS](./css) | CSS → Grida IR property mapping and TODO tracker |
+| [HTML](./html) | HTML element → Grida IR node mapping |
+| [SVG](./svg) | SVG → usvg → Grida IR mapping and TODO tracker |
## How to use these docs
-- **Status key:** ✅ mapped | ⚠️ partial | 🔧 IR exists, not wired | ❌ IR missing | 🚫 out of scope
-- Each page tracks what is implemented, what is partially done, and what is blocked by missing IR fields.
-- The **IR Gaps** section in each page identifies schema changes that would unblock further progress.
-- For the on-disk `.grida` file format (FlatBuffers schema), see `format/grida.fbs`.
+The CSS, HTML, and SVG trackers use this status key: ✅ mapped | ⚠️ partial |
+🔧 IR exists, not wired | ❌ IR missing | 🚫 out of scope. Their **IR Gaps**
+sections identify schema changes that would unblock further progress.
+
+For the on-disk `.grida` file format, see the [FlatBuffers
+schema](https://github.com/gridaco/grida/blob/main/format/grida.fbs).
## Related
-- **FlatBuffers schema:** `format/grida.fbs` — the canonical on-disk file format
-- **Rust runtime model:** `crates/grida/src/node/schema.rs`
-- **TypeScript model:** `packages/grida-canvas-schema/grida.ts`
-- **HTML import pipeline:** `crates/grida/src/import/html/`
-- **SVG import pipeline:** `crates/grida/src/import/svg/`
+- **FlatBuffers schema:** [canonical on-disk format](https://github.com/gridaco/grida/blob/main/format/grida.fbs)
+- **Rust runtime model:** [node schema](https://github.com/gridaco/grida/blob/main/crates/grida/src/node/schema.rs)
+- **TypeScript model:** [canvas schema](https://github.com/gridaco/grida/blob/main/packages/grida-canvas-schema/grida.ts)
+- **HTML import pipeline:** [HTML importer](https://github.com/gridaco/grida/tree/main/crates/grida/src/import/html)
+- **SVG import pipeline:** [SVG importer](https://github.com/gridaco/grida/tree/main/crates/grida/src/import/svg)
diff --git a/docs/wg/research/motion-graphics/_category_.json b/docs/wg/research/motion-graphics/_category_.json
new file mode 100644
index 0000000000..d76c96a1ac
--- /dev/null
+++ b/docs/wg/research/motion-graphics/_category_.json
@@ -0,0 +1,3 @@
+{
+ "label": "Motion graphics"
+}
diff --git a/docs/wg/research/motion-graphics/index.md b/docs/wg/research/motion-graphics/index.md
new file mode 100644
index 0000000000..be6868db60
--- /dev/null
+++ b/docs/wg/research/motion-graphics/index.md
@@ -0,0 +1,616 @@
+---
+title: "Motion graphics authoring landscape"
+description: "A qualitative study of animation models and authoring ergonomics across SVG and Web Animations, Lottie, After Effects, Blender, Rive, dotLottie, Cavalry, Apple Motion, PowerPoint, and Keynote."
+keywords:
+ - motion graphics
+ - animation authoring
+ - keyframes
+ - timeline
+ - lottie
+ - after effects
+ - blender
+ - rive
+ - cavalry
+tags:
+ - internal
+ - research
+ - authoring
+ - editor
+ - rendering
+format: md
+---
+
+# Motion graphics authoring landscape
+
+**Study type:** Qualitative product, format, and workflow survey. This is not a
+controlled usability study, a market-share analysis, or a normative animation
+specification.
+
+## Scope and method
+
+This study asks two bounded questions:
+
+1. Which persistent animation concepts distinguish professional motion-graphics
+ systems from basic property animation?
+2. Which apparent capabilities belong to the saved/evaluated model, and which
+ are editor ergonomics over that model?
+
+The comparison covers open web standards, interchange formats, timeline-based
+compositors, general digital-content-creation tools, interactive vector
+runtimes, procedural 2D tools, and presentation software. “Market” denotes
+these product and workflow categories; it does not imply revenue, adoption, or
+ranking claims.
+
+Sources are the projects' official specifications, manuals, and support
+documentation, consulted in July 2026. The descriptions below use each
+system's own terms. Product behavior outside the cited material was not assumed.
+
+## Landscape by role
+
+The systems occupy different positions in the path from authoring to delivery:
+
+| system | primary role in the landscape | characteristic model |
+| -------------------- | --------------------------------------------- | ------------------------------------------------------------------ |
+| SVG / Web Animations | open document and browser animation semantics | timeline + effect timing + typed property interpolation |
+| Lottie | portable vector-animation interchange | frame-based compositions, layers, and animatable properties |
+| Adobe After Effects | layered motion-graphics authoring/compositing | compositions, layers, property tracks, effects, and expressions |
+| Blender | general DCC animation and rigging | Actions, F-Curves, NLA strips, drivers, and constraints |
+| Rive | interactive vector content runtime | timelines, state machines, blend states, layouts, and data binding |
+| dotLottie | packaged Lottie delivery and interactivity | multiple Lottie animations, themes, assets, and state machines |
+| Cavalry | procedural 2D motion design | attributes, keyframes, Behaviours, Utilities, and generators |
+| Apple Motion | real-time motion graphics and templates | keyframes, Behaviors, rigs, replicators, and published controls |
+| PowerPoint / Keynote | presentation choreography | ordered effects, cues, and state-to-state slide transitions |
+
+This role distinction matters. An authoring project can retain controls,
+expressions, caches, and editor state that an interchange file intentionally
+omits. A runtime asset can retain interaction logic that a linear video export
+does not need.
+
+## A convergent evaluation shape
+
+Despite different vocabulary, the systems repeatedly separate the same broad
+stages:
+
+```text
+authoring views
+ timeline · dope sheet · graph editor · state graph · canvas handles
+ |
+ v
+persistent animation semantics
+ tracks · clips · local time · value sources · constraints · controllers
+ |
+ v
+evaluation at time t + declared inputs
+ |
+ v
+sampled scene state
+ |
+ v
+layout / composition / raster / presentation
+```
+
+The key difference between basic animation and motion-graphics systems is not
+the existence of an easing curve. It is how many persistent value sources can
+be composed, reused, retimed, selected, parameterized, and rendered.
+
+## The established keyframe kernel
+
+### SVG and Web Animations
+
+SVG declarative animation provides `animate`, `set`, `animateTransform`, and
+`animateMotion`. SVG processing modes can enable or disable declarative
+animation rather than merely pausing it at zero. The model includes base and
+animated values, interval timing, repeat behavior, motion paths, and multiple
+contributions to a target.
+
+[Web Animations](https://www.w3.org/TR/web-animations-1/) generalizes the
+browser model into timelines, animation effects, effect timing, playback
+control, keyframes, and composite operations. It separates:
+
+- the timeline that supplies time;
+- the timing model that converts time to effect progress;
+- keyframe interpolation that converts progress to a property value;
+- the effect stack that combines contributions with an underlying value.
+
+The [SVG processing-mode definition](https://www.w3.org/TR/SVG2/conform.html)
+and [CSS motion-path model](https://www.w3.org/TR/motion-1/) also make two
+boundaries explicit: animation can be a processor capability, and path motion
+has anchor, distance, and orientation semantics beyond x/y interpolation.
+
+These standards provide a mature property-animation kernel. They do not
+provide the complete composition, template, procedural, and authoring-project
+models found in dedicated motion tools.
+
+### Lottie
+
+Lottie is a JSON-based interchange format for animated vector graphics. Its
+top-level animation declares a frame rate, in/out points, dimensions, layers,
+assets, markers, and replaceable slots. Layers add local start and visibility
+times, parenting, transforms, masks, mattes, and precomposition references.
+Precomposition layers support time stretch and an animatable time-remap
+property. See the official [composition](https://lottie.github.io/lottie-spec/latest/specs/composition/)
+and [layer](https://lottie.github.io/lottie-spec/latest/specs/layers/)
+specifications.
+
+An animatable Lottie property switches between a static value and an ordered
+keyframe array. Keyframes carry frame time, hold behavior, temporal easing
+tangents, typed values, and—where applicable—spatial tangents. Scalar, vector,
+position, color, gradient, and Bézier-shape properties specialize this common
+shape. See [Lottie properties](https://lottie.github.io/lottie-spec/latest/specs/properties/).
+
+Lottie also persists motion-graphics structures that are absent from basic SVG
+animation: nested compositions, null layers, track mattes, trim paths, shape
+modifiers, auto-orient, and time remapping. Its shape model distinguishes path
+geometry, styles, modifiers, grouping, and their order of application. See the
+[Lottie shape model](https://lottiefiles.github.io/lottie-spec/specs/shapes/).
+
+The formal specification remains deliberately narrower than the historical
+ecosystem. Lottie 1.0 covered features that were commonly implemented and
+behaved consistently across players; its published feature list says that not
+all features are covered. Expressions remain a commonly used extension outside
+the normative format and can execute code. See the
+[Lottie specification changelog](https://lottie.github.io/changelog/) and
+[format security considerations](https://lottie.github.io/lottie-spec/dev/specs/format/).
+
+This makes Lottie an instructive interoperability boundary: the portable
+subset is defined by shared renderer behavior, not by everything an upstream
+authoring application can express.
+
+## Layered composition and reuse
+
+### Adobe After Effects
+
+After Effects organizes animation around compositions, ordered layers, and
+animatable properties. Its Graph Editor distinguishes value graphs from speed
+graphs, and its keyframe model distinguishes temporal interpolation from
+spatial interpolation. Temporal modes include linear, Bézier variants, and
+hold; spatial properties additionally support spatial paths and roving
+keyframes. See [keyframe interpolation](https://helpx.adobe.com/uk/after-effects/using/keyframe-interpolation.html).
+
+A nested composition is both reusable content and a layer in another
+composition. The nested source retains its own layer structure and timing; the
+containing composition supplies another layer transform and can alter render
+ordering. A network of nested compositions therefore forms a hierarchy of
+local scene and time domains. See
+[precomposing and nesting](https://helpx.adobe.com/uk/after-effects/using/precomposing-nesting-pre-rendering.html).
+
+Two template features expose distinctions that ordinary tracks do not cover:
+
+- **Essential Properties** expose selected source properties on a nested
+ composition. An instance can override a source value, keyframes, or an
+ expression without changing the reusable source.
+- **Responsive Design — Time** marks protected regions whose duration remains
+ fixed while unprotected regions stretch when a nested composition or Motion
+ Graphics template changes duration.
+
+See [Essential Properties](https://helpx.adobe.com/uk/after-effects/using/essential-properties.html)
+and [responsive-time regions](https://helpx.adobe.com/after-effects/using/responsive-design.html).
+
+After Effects also retains domain-specific motion structures:
+
+- shape-layer paths, fills, strokes, and ordered path operations including
+ trim, repeater, offset, wiggle, and boolean operations;
+- text animator groups whose selectors distribute an effect across
+ characters, characters excluding spaces, words, or lines;
+- expressions that can drive any keyframe-capable property and link properties
+ across layers or compositions;
+- composition-level motion-blur parameters including shutter angle, shutter
+ phase, minimum samples, and an adaptive sample limit.
+
+These structures are described in the official documentation for
+[shape operations](https://helpx.adobe.com/after-effects/desktop/drawing-painting-and-paths/shapes-and-shape-attributes/shape-attributes-paint-operations-path.html),
+[text animators and selectors](https://helpx.adobe.com/after-effects/desktop/animating-text/text-animation/animating-text.html),
+[expressions](https://helpx.adobe.com/after-effects/using/edit-expressions.html),
+and [motion-blur sampling](https://helpx.adobe.com/sg/after-effects/using/assorted-animation-tools.html).
+
+The persistent model is consequently broader than a collection of keyframes.
+It includes nested time, ordered geometry operators, selection functions over
+generated sub-elements, dependent property values, and temporal rendering.
+
+### Blender
+
+Blender divides reusable animation from the objects that consume it:
+
+- an **F-Curve** describes one property's value as a function of time;
+- an **Action** is a data-block containing animation channels and their
+ F-Curves;
+- an **NLA strip** references an Action and places, scales, repeats, and blends
+ it in a larger timeline;
+- **NLA tracks** sequence strips and allow several Actions to contribute at the
+ same time;
+- a **Driver** derives one property from variables, built-in functions, or a
+ scripted expression, then may map the result through an F-Curve;
+- a **Constraint** derives transforms or other constrained values, with an
+ ordered constraint stack.
+
+The [F-Curve introduction](https://docs.blender.org/manual/en/latest/editors/graph_editor/fcurves/introduction.html),
+[Actions manual](https://docs.blender.org/manual/en/latest/animation/actions.html),
+[NLA track documentation](https://docs.blender.org/manual/en/latest/editors/nla/tracks.html),
+[Drivers introduction](https://docs.blender.org/manual/en/latest/animation/drivers/introduction.html),
+and [constraint-stack documentation](https://docs.blender.org/manual/en/latest/animation/constraints/interface/stack.html)
+describe these layers.
+
+The editors correspond to different views of that data:
+
+- the Dope Sheet presents a scene-wide, timing-oriented view of keys;
+- the Action Editor focuses on one Action;
+- the Graph Editor exposes F-Curve values, interpolation, easing, handles,
+ extrapolation, and modifiers;
+- the NLA Editor edits clip placement, scale, repetition, influence, and blend;
+- the Drivers Editor exposes property dependencies and their mapping function.
+
+Blender visually distinguishes unanimated properties, properties keyed at the
+current frame, properties keyed elsewhere, values changed away from a keyed
+sample, and driver-controlled values. This makes the source of a displayed
+value inspectable from the ordinary property interface. See the
+[animation state-color reference](https://docs.blender.org/manual/en/latest/animation/introduction.html).
+
+Blender's Bake Action operation samples the evaluated result after F-Curve
+modifiers, drivers, and constraints and writes ordinary keyframes. Baking is
+therefore an explicit boundary between a rich procedural project and a simpler
+sampled representation. The same documentation notes that scripted drivers
+outside a restricted expression subset use Python, run more slowly, and pose a
+security risk for untrusted files.
+
+## Procedural and interactive value sources
+
+### Rive and dotLottie
+
+Rive separates Design mode from Animate mode. An artboard can own multiple
+timeline animations and multiple state machines. Selecting a timeline presents
+a timeline editor; selecting a state machine presents a graph editor. See
+[Design versus Animate mode](https://rive.app/docs/editor/fundamentals/design-vs-animate-mode).
+
+A Rive state machine contains layers, states, transitions, conditions, and
+inputs. Timeline animations become states; blend states combine several
+timelines using numeric inputs; multiple state-machine layers can contribute to
+the same artboard with declared layer precedence. Rive's current data-binding
+model uses typed view-model properties that can drive state transitions, blend
+weights, and bindable editor properties. See
+[state-machine structure](https://rive.app/docs/editor/state-machine/state-machine),
+[blend states](https://rive.app/docs/editor/state-machine/states),
+[state-machine layers](https://rive.app/docs/editor/state-machine/layers), and
+[data binding](https://rive.app/docs/editor/data-binding/overview).
+
+At runtime a state machine advances with elapsed time, evaluates animations,
+transitions, and data changes, and can settle when no future change is pending.
+External changes unsettle it. This differs from sampling a stateless linear
+clip: the controller has persistent state in addition to timeline time. See
+[state-machine playback](https://rive.app/docs/runtimes/state-machines).
+
+Rive also animates responsive layout. A layout container can define how its
+size and child positions interpolate when content reflows, and children can
+inherit the parent's transition parameters. This is derived motion over layout
+results rather than a fixed coordinate track. See
+[Rive layout animation](https://rive.app/docs/editor/layouts/layout-animation).
+
+dotLottie packages one or more Lottie animations with assets, fonts, themes,
+and optional state machines. Its state machine format defines playback states,
+global states, transitions, guards, inputs, actions, playback modes, speed, and
+marker-selected segments. This places interaction and playback control around
+linear Lottie animations instead of adding another property-keyframe grammar.
+See the [dotLottie 2.0 specification](https://dotlottie.io/spec/2.0/).
+
+### Cavalry
+
+Cavalry combines keyframed Attributes with procedural Behaviours, Utilities,
+generators, constraints, and connections between layers. Its Graph Editor can
+show both ordinary keyframe curves and the inherited curve produced when, for
+example, a Noise Behaviour drives an Attribute. See the
+[Cavalry Graph Editor](https://cavalry.studio/docs/user-interface/menus/window-menu/scene-window/graph-editor/).
+
+The Graph Editor supports linear, Bézier, and step interpolation; curve
+looping, loop-with-offset, and oscillation; curve ghosting; key alignment; and
+time/value scaling. In-viewport motion paths expose spatial keyframes and
+velocity controls. See
+[Cavalry motion paths](https://cavalry.studio/docs/user-interface/menus/window-menu/viewport/motion-paths/).
+
+Animation Control remaps a complete animation curve to a normalized percentage
+and permits another Attribute to drive that percentage. The Bake Animation
+command samples procedural animation from Behaviours or Magic Easing into
+keyframes. See [Animation Control](https://cavalry.studio/docs/nodes/utilities/animation-control/)
+and the [Animation menu](https://cavalry.studio/docs/user-interface/menus/animation-menu/).
+
+The coexistence of previewable procedural curves and a bake operation makes
+the distinction between editable value generators and sampled interchange
+explicit.
+
+### Apple Motion
+
+Apple Motion presents keyframes and Behaviors as complementary animation
+sources. Keyframes specify exact values at exact frames. Behaviors generate
+values over a duration and include motion, simulation, parameter, text, audio,
+camera, particle, and replicator families. Behaviors can affect object
+properties, other objects, or parameters of other Behaviors, and their order of
+operations with keyframes is significant. See the
+[Motion User Guide](https://help.apple.com/motion/mac/) and
+[Behavior application model](https://help.apple.com/motion/mac/5.0/help/English/en/motion/usermanual/chapter_9_section_3.html).
+
+Parameter Behaviors include operations such as audio response, averaging,
+clamping, linking, quantization, randomization, rate, reverse, and wriggle.
+Their results can be combined with keyframes under declared apply modes. See
+[Parameter Behaviors](https://help.apple.com/motion/mac/5.0/help/English/en/motion/usermanual/chapter_9_section_10.html).
+
+Motion rigs expose a small set of widgets—sliders, pop-up menus, and
+checkboxes—that can control many internal parameters. Published widgets and
+parameters form the editing surface presented to Final Cut Pro users while the
+template retains its full internal construction. See
+[rigs and widgets](https://help.apple.com/motion/mac/5.0/help/English/en/motion/usermanual/chapter_10_section_1.html)
+and [published template parameters](https://help.apple.com/motion/mac/5.0/help/English/en/motion/usermanual/chapter_11_section_15.html).
+
+Template timing uses markers to distinguish regions that may stretch, remain
+fixed, or loop when a template is applied to media of another duration. The
+manual recommends Behaviors for many template animations because they are less
+dependent on specific keyframe times. See
+[template animation and timing](https://help.apple.com/motion/mac/5.0/help/English/en/motion/usermanual/chapter_11_section_16.html).
+
+## Presentation-oriented authoring
+
+PowerPoint and Keynote reduce the amount of timeline manipulation required for
+common presentation motion.
+
+PowerPoint's Morph transition derives an animation from corresponding objects
+on adjacent slides. Authors duplicate or otherwise construct a destination
+slide, then edit endpoint position, size, rotation, color, or content.
+PowerPoint can infer correspondence and also permits explicit correspondence by
+giving one object on each slide the same `!!`-prefixed name. See
+[Morph](https://support.microsoft.com/en-us/powerpoint/training/use-the-morph-transition-in-powerpoint)
+and [Morph correspondence rules](https://support.microsoft.com/en-us/powerpoint/morph-transition-tips-and-tricks).
+
+Within a slide, PowerPoint orders effects in an Animation Pane and offers cue
+relationships—On Click, With Previous, and After Previous—plus duration,
+delay, repeat, and rewind. Several effects can target one object and may run
+concurrently. See
+[animation start and speed](https://support.microsoft.com/en-US/PowerPoint/set-the-start-time-and-speed-of-an-animation-effect)
+and [multiple effects](https://support.microsoft.com/en-us/powerpoint/apply-multiple-animation-effects-to-one-object).
+
+Keynote's Magic Move follows the same endpoint-state pattern: the transition is
+most effective when adjacent slides contain a common object whose position or
+appearance changes. See
+[Keynote transitions](https://support.apple.com/guide/keynote/add-transitions-tanff5ae749e/mac).
+
+These workflows reveal two authoring abstractions that are distinct from a
+curve editor:
+
+- **state-difference authoring**, where correspondence and endpoint changes
+ generate intermediate motion;
+- **cue-relative sequencing**, where effects are ordered by user actions or
+ neighboring effects rather than only by absolute timestamps.
+
+## Motion semantics beyond ordinary keyframes
+
+The systems repeatedly persist the following concepts in addition to basic
+property tracks:
+
+| concept | manifestations in the surveyed systems |
+| ------------------------------ | ----------------------------------------------------------------------------------------------- |
+| reusable animation unit | Blender Action · Rive timeline · nested AE composition · Lottie animation/precomposition |
+| clip instance | Blender NLA strip · AE/Lottie precomposition layer · Rive state referencing a timeline |
+| local time mapping | NLA strip scale/repeat · AE/Lottie start/stretch/time remap · responsive protected regions |
+| multiple value contributions | Web Animations effect stack · NLA tracks · Rive layers/blends · Motion Behavior apply modes |
+| derived property value | Blender Driver/Constraint · AE expression · Cavalry connection/Behaviour · Motion Link Behavior |
+| generated-element selection | AE text selectors · shape repeaters · Cavalry per-character/group and falloff tools |
+| ordered geometry operation | AE/Lottie trim/repeater/offset/wiggle/boolean shape operators |
+| interaction controller | Rive state machine · dotLottie state machine · PowerPoint click cues |
+| typed external parameter | Rive view model · AE Essential Properties · Motion published widgets |
+| responsive spatial transition | Rive layout animation · PowerPoint/Keynote state-to-state morph |
+| responsive temporal transition | AE protected regions · Motion template markers |
+| temporal rendering | AE shutter angle/phase and sample limits |
+| simplification boundary | Blender Bake Action · Cavalry Bake Animation · video/raster export |
+
+Some concepts can be flattened for a particular delivery target. Flattening
+changes the artifact, however: a baked per-character reveal no longer adapts to
+different text; baked layout motion no longer responds to reflow; baked
+procedural noise no longer exposes its seed or parameters.
+
+## Authoring ergonomics
+
+### Property-local animation state
+
+The most immediate animation affordance is attached to an ordinary property,
+not hidden in a separate timeline. After Effects exposes a stopwatch; Rive
+shows a key icon on animatable inspector properties; Blender colors properties
+according to whether they are keyed, changed, or driven. These affordances
+answer three frequent questions without opening another editor:
+
+- Can this property animate?
+- Does it have a key at the current time?
+- Which mechanism currently controls its displayed value?
+
+### Multiple views over one channel model
+
+Timing and value shape are edited in separate views because they are different
+tasks:
+
+- a Dope Sheet or layer-bar timeline emphasizes order, spacing, duration, and
+ synchronization;
+- a Graph Editor emphasizes value, velocity, easing, tangent shape, and
+ extrapolation;
+- an in-canvas motion path emphasizes spatial trajectory and spatial tangents;
+- an NLA or clip editor emphasizes reuse, local time, repetition, and blend;
+- a state graph emphasizes modes, transitions, conditions, and interaction.
+
+The views are not alternative saved animation systems. They expose different
+projections of tracks, clips, paths, or controllers.
+
+### Selection and disclosure
+
+Professional timelines can contain many channels. The surveyed tools
+use selection, filtering, hierarchy, disclosure triangles, solo/mute, and
+“show animated” modes to control density. Blender separates a scene-wide Dope
+Sheet from the single-Action editor; Cavalry loads animated Attributes based on
+selected layers, Attributes, or keyframes; AE exposes nested property groups.
+
+This is an ergonomic response to a broad animatable-property model: the
+underlying data can be general while the working set remains local.
+
+### Temporal and spatial manipulation
+
+After Effects and Lottie store temporal easing separately from spatial
+tangents. Cavalry edits motion paths directly in the viewport while its Graph
+Editor edits value curves. This avoids forcing “how fast along the path?” and
+“what path?” into one control.
+
+Bulk curve operations are also common: align, scale, reverse, smooth, bake,
+reduce, copy easing, change handle type, and loop. Blender includes multiple
+smoothing and blending operations; Cavalry exposes transform boxes, ghost
+curves, easing copy/paste, and loop modes. These are mostly authoring
+operations over persistent curves rather than additional playback semantics.
+
+### Reuse without exposing internal complexity
+
+After Effects Essential Properties, Motion rigs, and Rive data binding all
+separate an artifact's internal property graph from the controls intended for
+another editor or application. A reusable animation is therefore not merely a
+copied subtree: it has an intentional parameter surface, instance overrides,
+and a relationship to its source.
+
+### Procedural inspection and baking
+
+Procedural systems are easier to author when their evaluated result remains
+visible in familiar curve and canvas tools. Cavalry previews inherited
+procedural curves in the Graph Editor. Blender displays driver-controlled
+property state and provides a Drivers Editor. Both provide baking operations
+that replace a dependency graph with sampled keys when a simpler representation
+is needed.
+
+### Endpoint and cue authoring
+
+Presentation tools optimize for authors who know the desired states and
+sequence but do not want to manipulate curves. Morph/Magic Move derives motion
+from two scene states; On Click/With Previous/After Previous derives start time
+from cue order. These are high-level authoring operations with an inspectable
+result, not a replacement for lower-level timing and interpolation semantics.
+
+### Motion accessibility
+
+Interactive motion has an environmental dimension absent from traditional
+linear video. The `prefers-reduced-motion` media feature reports a request to
+remove or replace nonessential motion. WCAG guidance says interaction-triggered
+nonessential motion should be disableable and automatically moving content may
+need pause, stop, or hide controls. See
+[Media Queries Level 5](https://www.w3.org/TR/mediaqueries-5/),
+[WCAG animation from interactions](https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions),
+and [WCAG pause, stop, hide](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide).
+
+This introduces host policy, alternative motion, and playback-control concerns
+beyond the animation curve itself.
+
+## Tooling versus persisted semantics
+
+The survey supports a practical classification:
+
+| capability | classification |
+| ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
+| timeline, Dope Sheet, Graph Editor | editor views over persistent channels |
+| auto-key/record | authoring operation that inserts or updates keys |
+| snapping, easing presets, key alignment | authoring operations |
+| curve ghosting and motion-path overlays | derived editor visualization |
+| curve smoothing/reduction | authoring transformation; result persists as changed keys |
+| bake | authoring/export transformation between rich evaluation and sampled keys |
+| clip/Action/precomposition | persisted and evaluated semantics |
+| local time, repeat, stretch, remap | persisted and evaluated semantics |
+| effect/track blending | persisted and evaluated semantics |
+| driver, constraint, expression, Behavior | persisted semantics when retained; removable only through baking |
+| text selector, repeater, falloff | persisted semantics for content that remains dynamic |
+| state machine and data binding | persisted controller semantics |
+| responsive layout motion | persisted semantics when layout/content can change at runtime |
+| motion blur and shutter | persisted render semantics or an explicitly external render profile |
+| state-to-state Morph | may be compiled authoring output for fixed states; remains semantic when correspondence is evaluated live |
+
+The boundary is observable: if reopening, retargeting, changing dynamic
+content, arbitrary-time evaluation, or rendering in another implementation
+must preserve a capability, that capability cannot exist only in editor UI.
+
+## Cross-project capability comparison
+
+This table records the characteristic mechanism exposed by the cited systems.
+An em dash means the study did not establish a comparable first-class
+mechanism; it does not prove that no extension, plug-in, or host integration
+exists.
+
+| system | property animation | reuse and local time | derived/procedural values | interaction/control | responsive/template mechanism | simplification/delivery boundary |
+| -------------------- | ---------------------------- | ---------------------------------------- | ----------------------------------------- | --------------------------------- | ------------------------------------ | -------------------------------- |
+| SVG / Web Animations | effects and typed properties | named keyframes; no nested clip model | CSS/script outside the declarative core | event timing and host APIs | processor and media-query policy | document processing mode |
+| Lottie | typed animatable properties | precompositions, stretch, and time remap | shape modifiers; expressions extension | separate dotLottie state machine | slots and dotLottie themes | interchange asset |
+| After Effects | property tracks | nested compositions and local time | expressions, effects, and shape operators | host/scripting integration | Essential Properties; protected time | render/export |
+| Blender | F-Curves | Actions and NLA strips/tracks | drivers, constraints, and modifiers | external/game logic; rig controls | custom properties and rigs | Bake Action |
+| Rive | timeline keys | timelines referenced by states | constraints, blends, and data binding | layered state machines | view models and layout animation | runtime/export |
+| Cavalry | Attribute keyframes | Animation Control remapping | Behaviours, Utilities, and connections | — | — | Bake Animation |
+| Apple Motion | parameter keyframes | reusable Behaviors and templates | Behaviors, rigs, and replicators | published host controls | published widgets and timing markers | convert/render/export |
+| PowerPoint / Keynote | ordered effects | slides and transition/effect duration | transition presets and state morph | click and relative cues | object correspondence | video/export |
+
+The entries are not claims of full equivalence. For example, an NLA blend, a
+Web Animations composite operation, and a Rive blend state all combine motion,
+but they operate at different scopes and use different value rules.
+
+## Findings
+
+1. **The keyframe kernel is convergent.** Ordered time/value samples,
+ hold/linear/Bézier interpolation, easing, repetition, and typed property
+ interpolation recur across standards and tools.
+2. **Professional motion projects retain more than keyframes.** Reusable clips,
+ nested local time, value-source graphs, selection functions, shape
+ operators, controllers, and render-time sampling are persistent semantics.
+3. **Reuse introduces an instance model.** Actions, precompositions, timelines,
+ and templates separate reusable sources from placed instances, local time,
+ and overrides.
+4. **Procedural authoring and portability coexist through baking.** Blender,
+ Cavalry, and video/render export expose a boundary between rich editable
+ evaluation and simpler sampled delivery.
+5. **Dynamic content makes some baking lossy.** Per-character text selectors,
+ layout transitions, repeaters, and data-bound states cannot remain adaptive
+ after they are flattened to fixed element tracks.
+6. **Linear animation and interaction are separate layers.** Rive and dotLottie
+ place state machines around reusable timeline animations rather than
+ replacing the timeline/keyframe model.
+7. **Motion-graphics ergonomics are plural views, not plural truths.** Dope
+ sheets, graph editors, motion paths, NLA editors, and state graphs expose
+ different questions over shared persistent structures.
+8. **Templates add semantic responsiveness.** Exposed parameters, instance
+ overrides, protected time regions, and duration markers allow one authored
+ artifact to survive new content and new durations.
+9. **Presentation tools trade curve detail for endpoint and cue authoring.**
+ State correspondence and relative sequencing make common choreography
+ accessible without erasing the need for an evaluated intermediate result.
+10. **Temporal rendering is distinct from temporal evaluation.** Motion blur
+ samples an evaluated scene over a shutter interval; it is not another
+ interpolation curve.
+11. **Interactive motion carries accessibility policy.** Reduced-motion,
+ pause, and stop behavior depend on environment and host control as well as
+ authored motion.
+
+## Primary source map
+
+### Standards and interchange
+
+- [SVG 2 declarative-animation processing modes](https://www.w3.org/TR/SVG2/conform.html)
+- [Web Animations](https://www.w3.org/TR/web-animations-1/)
+- [CSS Motion Path](https://www.w3.org/TR/motion-1/)
+- [Lottie Animation Community](https://lottie.github.io/)
+- [Lottie 1.0 feature changelog](https://lottie.github.io/changelog/)
+- [Lottie composition](https://lottie.github.io/lottie-spec/latest/specs/composition/)
+- [Lottie properties](https://lottie.github.io/lottie-spec/latest/specs/properties/)
+- [Lottie layers](https://lottie.github.io/lottie-spec/latest/specs/layers/)
+- [dotLottie 2.0](https://dotlottie.io/spec/2.0/)
+
+### Authoring systems
+
+- [Adobe After Effects keyframe interpolation](https://helpx.adobe.com/uk/after-effects/using/keyframe-interpolation.html)
+- [Adobe After Effects precompositions](https://helpx.adobe.com/uk/after-effects/using/precomposing-nesting-pre-rendering.html)
+- [Adobe After Effects Essential Properties](https://helpx.adobe.com/uk/after-effects/using/essential-properties.html)
+- [Adobe After Effects responsive time](https://helpx.adobe.com/after-effects/using/responsive-design.html)
+- [Blender animation manual](https://docs.blender.org/manual/en/latest/animation/index.html)
+- [Blender Graph Editor](https://docs.blender.org/manual/en/latest/editors/graph_editor/index.html)
+- [Blender NLA Editor](https://docs.blender.org/manual/en/latest/editors/nla/index.html)
+- [Rive editor and runtime documentation](https://rive.app/docs)
+- [Cavalry documentation](https://cavalry.studio/docs/)
+- [Apple Motion User Guide](https://help.apple.com/motion/mac/)
+- [PowerPoint Morph](https://support.microsoft.com/en-us/powerpoint/training/use-the-morph-transition-in-powerpoint)
+- [Keynote transitions](https://support.apple.com/guide/keynote/add-transitions-tanff5ae749e/mac)
+
+### Accessibility
+
+- [Media Queries Level 5 — reduced motion](https://www.w3.org/TR/mediaqueries-5/)
+- [WCAG 2.2 — animation from interactions](https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions)
+- [WCAG 2.2 — pause, stop, hide](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide)
diff --git a/model-v2/.gitignore b/model-v2/.gitignore
new file mode 100644
index 0000000000..2f7896d1d1
--- /dev/null
+++ b/model-v2/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/model-v2/README.md b/model-v2/README.md
new file mode 100644
index 0000000000..fb8f27b0d7
--- /dev/null
+++ b/model-v2/README.md
@@ -0,0 +1,130 @@
+# model-v2 — node geometry / layout / transform model redesign
+
+Workbench for the fundamental redesign of the Grida node model: how a node's
+geometry, position, size, rotation/transform, and layout participation are
+represented — in the **Rust engine** (`crates/grida`) and the **format spec**
+(`format/grida.fbs`). Other seams (TS editor, WASM bindings, importers) follow
+after the model lands; they are out of scope here.
+
+> **Branch note.** This directory lives on the `model-v2-anchor` branch as a
+> working snapshot — tracking issue:
+> [gridaco/grida#957](https://github.com/gridaco/grida/issues/957) (pinned).
+> The plan: finish the feel pass on the spike, then propose an RFC with this
+> branch as the textbook, then start the legacy migration/rebuild (weeks
+> out). Nothing here ships; no production code is touched by this branch.
+
+## Run
+
+```sh
+# the lab (the model's single source of truth) — full conformance suite
+cd model-v2/a/lab && cargo test
+
+# the interactive spike (native skia window on the model)
+cd model-v2/a/spike-canvas && cargo run --release
+
+# the demo pages (proof, model walkthrough, edge cases, DEC-0 fork, free editing)
+python3 -m http.server 4173 --directory model-v2/a/.preview
+```
+
+## Why this exists
+
+The current system answers the same question three different ways:
+
+- leaf nodes: baked `AffineTransform` + `size`
+- containers: `position` enum + `rotation: f32` scalar + `layout_dimensions`
+- format spec (`LayerTrait`): `layout` + `post_layout_transform` (unimplemented, self-flagged provisional)
+
+reconciled at runtime by a lossy, branchy resolver. This was never reconciled
+because the underlying questions were never decided. This directory decides
+them — problems first, then candidates, then spec.
+
+## Phase discipline
+
+| phase | artifact | status |
+| ----------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 1. Problems & harnesses | `problems.md`, `harnesses.md`, `study.md` | stable draft |
+| 2. Candidate models | `paradigm.md`, `axes.md`, `models/*`, `finale.md`, `triage.md` | **DECIDED — `anchor`** (+5 triage amendments) |
+| 3. Spec | normative doc + `grida.fbs` draft | **experiments RUN, model PROVEN** — E1–E10 complete with verdicts; **DEC-0 decided: VISUAL-ONLY rotation (the CSS framing), CSS-pure sizing** ([`a/dec0-visual-only.md`](./a/dec0-visual-only.md)); flips built (E-A14, cross-zero resize); conformance lab; native interactive spike ([`a/spike-canvas/`](./a/spike-canvas/)); open calls parked in [`a/DECISIONS.md`](./a/DECISIONS.md). Remaining: fold deltas into a normative rewrite of `models/a.md` + WG graduation |
+| 4. Runtime | `crates/grida` implementation | **day-1 engine skeleton BUILT** — [`engine/`](./engine) (`anchor-engine`): the `resolve → drawlist → paint` pipeline + query/journal/replay/damage sockets, spike re-hosted onto it, gate green (shots byte-identical, replay deterministic, budgets baselined); contracts in [`a/ENGINE.md`](./a/ENGINE.md). Migration into `crates/grida` follows; the spike's [`TEXTBOOK.md`](./a/spike-canvas/TEXTBOOK.md) is the reference |
+
+Ground rules:
+
+- **Problems before solutions.** When a new unclear part arises, it becomes a
+ catalog entry — not an inline patch to a proposal.
+- **Every claim is evidence-linked** to current code, the format spec, or a
+ studied peer system.
+- **No candidate survives without a harness run.** `harnesses.md` is the test
+ suite for designs.
+- An earlier in-chat probe sketched one candidate (scalars-canonical + per-axis
+ anchors + layout-visible rotation + a transform-quarantine node). It is
+ deliberately **not recorded here as a decision** — it re-enters in phase 2 as
+ one candidate among others, subject to the harnesses.
+
+## Files
+
+- [`problems.md`](./problems.md) — the problem catalog (P1–P11): each unclear
+ part stated precisely, with its tension, option space, and evidence.
+- [`harnesses.md`](./harnesses.md) — the constraints (H1–H10) any candidate
+ must pass, each with a concrete pass/fail probe, plus the tension map
+ between harnesses.
+- [`study.md`](./study.md) — comparative study of peer systems (CSS, SVG,
+ Flutter, SwiftUI, Figma, tldraw): facts we reason with, not designs we copy.
+- [`paradigm.md`](./paradigm.md) — phase-2 **candidate** paradigm
+ ("one box, one way"): nouns, laws, how each problem lands, trades declared,
+ falsification criteria. Not ratified.
+- [`finale.md`](./finale.md) — the phase-2 finale, **decided: `anchor`**;
+ preserved with the pre-decision concession bill and deciding question.
+- [`survey.md`](./survey.md) — the 32-question instrument itself, saved
+ clean (no answers, no verdict): administration rules, all questions with
+ options, and scoring guidance. Reusable for future re-runs or other
+ respondents.
+- [`triage.md`](./triage.md) — the 2026-07-07 run of the survey: answers,
+ scoring key, verdict, the five amendments, and the one open (tilted)
+ fork.
+- [`editor.md`](./editor.md) — the editor-experience operation catalog:
+ every gesture as **gesture → writes → effect → ripple** with stable
+ `OP-*` ids, the six operation laws (incl. the three sanctioned
+ state→intent bake moments), and per-op FORK marks.
+- [`conformance.md`](./conformance.md) — the model-agnostic test corpus:
+ metamorphic laws, per-area invariants + edge registries, the executable
+ merge matrix, and the compatibility checklist (CSS/Figma/SVG/current
+ engine) with Y / N-deviation / spectrum verdicts. FORK rows are the
+ finale's probes.
+- [`axes.md`](./axes.md) — the decision-space factoring: **Axis 1 = semantic
+ model** (`anchor` vs `bake` — decide first), **Axis 2 = representation &
+ mutation protocol** (struct vs sheet, key granularity — tunable after,
+ bounded by the atom rule). Re-scopes `sheet`; source of harnesses H11/H12.
+- [`a/`](./a/) — **the winner's workbench**: the experiment ledger E1–E10
+ (each with verdicts and lab tests), the decision register
+ ([`a/DECISIONS.md`](./a/DECISIONS.md)), the DEC-0 normative rules, the
+ ship-readiness census ([`a/LIMITS.md`](./a/LIMITS.md)), the peer-compat
+ matrix, the phase-4 engine layer programs with day-1 contracts
+ ([`a/ENGINE.md`](./a/ENGINE.md)), the Rust conformance lab
+ ([`a/lab/`](./a/lab/)), and the native interactive spike
+ ([`a/spike-canvas/`](./a/spike-canvas/)).
+- [`models/`](./models/) — concrete candidate models, one file each,
+ harness-scored, best-faith. Files keep letter slots (`a.md`, `b.md`, …);
+ the names are the working identifiers:
+ - [`models/a.md`](./models/a.md) — **`anchor`** (the anchored box model):
+ intent-canonical scalars, per-axis bindings, lens quarantine. Proposed
+ best fit.
+ - [`models/b.md`](./models/b.md) — **`sheet`** (the property sheet model):
+ CSS-faithful flat registry, rulebook conflicts, post-layout transforms.
+ - [`models/c.md`](./models/c.md) — **`bake`** (the materialized matrix
+ model): Figma-faithful matrix + state canonicalism, edit-time layout.
+ - [`models/d.md`](./models/d.md) — **`wire`** (the wired geometry model):
+ relational archetype — referent-general bindings, dataflow DAG, the WG
+ Level-4 destination. Priced and deferred; `anchor` grows into it
+ additively.
+
+## Relationship to existing docs
+
+- [`docs/wg/feat-layout/index.md`](../docs/wg/feat-layout/index.md) — the
+ anchor+flex+grid positioning vision (draft, PR #437). It covers positioning
+ intent only and is **silent on rotation/transform and their layout
+ coupling** — that gap is a large part of this catalog. When phase 3 produces
+ a spec, it graduates into `docs/wg/` under WG doctrine (code-agnostic) and
+ this directory's evidence links stay behind as the working record.
+- [`format/grida.fbs`](../format/grida.fbs) — the current archive draft; its
+ header rules (unset-vs-default, tables-over-structs, additive evolution) are
+ binding harnesses on whatever phase 3 encodes (see H9).
diff --git a/model-v2/a/.preview/edge.html b/model-v2/a/.preview/edge.html
new file mode 100644
index 0000000000..698d2d5ae3
--- /dev/null
+++ b/model-v2/a/.preview/edge.html
@@ -0,0 +1,4053 @@
+anchor — rotation × layout edge cases
+
+
+
+
+
+ model-v2 · anchor · edge cases — 2026-07-07
+
+
Rotation × layout, at the edges.
+
+ Nine scenes where the model’s laws intersect,
+ resolved by the actual Rust lab (every frame is engine output, not
+ a mockup). The question per scene: does it read naturally? The
+ discontinuity meter is the objective proxy — the biggest single-step
+ jump of any box through the whole sweep. Two scenes fail it; they are now
+ findings E-A11/E-A12, not footnotes. The ninth scene sweeps a drag,
+ not an angle — the cross-zero resize decision (DEC-9), three
+ policies side by side.
+
+
+
+
+
+
+
+ θ = 0°
+
+
+
+
+
+ container —
+ biggest single-step box jump this sweep
+ —
+ at
+
+
+
+
+
+
diff --git a/model-v2/a/.preview/fork.html b/model-v2/a/.preview/fork.html
new file mode 100644
index 0000000000..2c2052e5f1
--- /dev/null
+++ b/model-v2/a/.preview/fork.html
@@ -0,0 +1,6837 @@
+anchor — DEC-0: the framing fork, in action
+
+
+
+
+
+ model-v2 · anchor · DEC-0 — 2026-07-07
+
+
The framing fork, in action.
+
+ The same seven scenes, the same slider, resolved twice by the same lab:
+ left = layout-visible rotation (anchor as locked by E1;
+ Figma’s framing) · right = visual-only rotation (the
+ CSS framing — rotation as post-layout paint transform). Every frame
+ is real resolver output; the meters are measured on resolved world bounds.
+ Left tax: the fill×rotation pops (DEC-1). Right tax: overlap,
+ frozen containment, and broken Figma parity. Watch the meters.
+
+
+
diff --git a/model-v2/a/.preview/free.html b/model-v2/a/.preview/free.html
new file mode 100644
index 0000000000..8956352a94
--- /dev/null
+++ b/model-v2/a/.preview/free.html
@@ -0,0 +1,911 @@
+anchor — free context: the editing experience
+
+
+
+
+
+ model-v2 · anchor · free context — 2026-07-07
+
+
Without layout: the base case, hands on.
+
+ A live free-context document — no layout engine runs here;
+ resolution is bindings + one rotation scalar, mirrored from the
+ lab’s free-context rules. Drag bodies to move, the corner dot to
+ resize, the stick to rotate. Watch two things: the
+ IR stays SVG-shaped, and every gesture is a
+ counted set of scalar writes (never a matrix). Then drag the
+ artboard’s right edge: the green badge is
+ end-pinned and the amber bar is spanned — they respond
+ with zero writes, because position is a relation. That is what SVG
+ cannot say, and it is the on-ramp to layout-as-feature.
+
+ select a node — reads come from the resolved tier
+
+
+
+
the IR — canonical print, live
+
+
writes — per gesture, typed
+
no gestures yet
+
+
+
+
+ what this shows
+ Free context is not a degraded mode — it is the model’s ground
+ floor.
+ Pin Start is literally svg x/y; rotation is one scalar with
+ one pivot rule (box center — so rotate is a 1-field write, no x/y
+ compensation, and resize-after-rotate never drifts: R-4 commutes,
+ lab-tested). A group is a named set with a coordinate space; dragging
+ it is 2 writes on the group, its children untouched. What SVG has no words
+ for: end / center / span bindings — responsiveness without any
+ layout engine, which is why “svg-first, layout-as-feature” is a
+ gradient here, not a mode switch: free (bindings) → constrained
+ (end/center/span) → flex (a frame feature). The lens (general
+ transform) never appears in the base case — it exists only when you
+ import one.
+
+
+
gesture
+
writes
+
fields
+
+
+
move
+
2
+
x.offset, y.offset (delta-form; works under any anchor)
+
+
+
rotate (boxed)
+
1
+
rotation — center pivot needs no compensation
+
+
+
rotate (group, center-feel)
+
3
+
rotation, x, y — Figma’s trick over legible scalars
+ Every number and pixel on this page comes from the lab in
+ model-v2/a/ — the resolver that implements the anchor spec,
+ the sweeps it ran, and the agents that were tested against it. Three kinds
+ of proof, in order of strength:
+
+
+
+
+
runtime
+
it runs, live
+
+ The E1 demo below is the resolver's actual output, scrubbable. On your
+ machine:
+ cargo test →
+ 56/56.
+
+
+
+
image
+
it renders, exactly
+
+ SVG frames emitted by the resolver at key angles, magenta dashes =
+ computed world AABBs.
+
+ Same document, same resolver, one flag. Top:
+ anchor — the rotated card
+ participates by its oriented envelope, siblings make room, the hug
+ container breathes. Bottom:
+ CSS control — layout is frozen,
+ rotation is paint-only, and the red wash is sibling overlap that no
+ document field expresses.
+
+ anchor breathes 220→276.6 px, peak at
+ θ*=atan(h/w)≈59°; control flat
+
+
+
+
+
Sibling overlap area vs θ
+
+ anchor: zero at every angle · control: up to 1,830 px²
+
+
+
+
+
Third card position vs θ
+
+ smooth — max step 3.45 px per 2°, inside the
+ √(w²+h²)·Δθ = 4.07 px bound
+
+
+
+
+
Verdict
+
+ conformance R-3 and editor OP-ROT-2: POL → INV
+
+
+ Locked layout-visible. Zero overlap by construction and by
+ measurement; displacement continuous through
+ 0°/90°/180°; rotation stays a one-field write.
+ Breathing during a full spin (56.6 px) is a hug-container phenomenon
+ and is exactly why motion rotation targets a lens channel — the
+ two-lane rule, now measured rather than argued.
+
+
+
+
+
+
+
+
θ = 0° — resolver SVG output
+
+
+
+
+ θ = 45° — envelope 113.1×113.1
+
+
+
+
+
+ θ = 90° — slot is exactly 100×60
+
+
+
+
+
+
+
exhibit e3 — can a model predict it cold?
+
Two fresh agents, a 150-line grammar, 22/22 exact.
+
+ Three agents were handed only the grammar and six documents — no
+ tools, no resolver — and asked for every resolved box. The hardest
+ probe is the E1 rule itself (p5, below). Frontier agents matched the
+ resolver on all 22 values within 0.5 px.
+
+ Even the small-model control applied the rotation-in-flow
+ rule correctly — its misses were an off-by-one character
+ count and placing a box at slot-start instead of slot-center. Slips, not
+ structural confusion. Records:
+ e3-text-ir/truth.txt · predictions/*
+
+
+
+
+
exhibit e2 — the file format
+
Encode → decode → encode: byte-identical.
+
+ The quartet document through flatc 25.12: JSON → binary → JSON
+ → binary produces the same bytes. And "unset" is structural —
+ read the decoded output:
+
+
+
// grow-text node — decoded JSON
+{
+ "id": 5, // no x, no y keys at all
+ "grow": 1.0// ⇒ Pin{Start,0} by rule
+}
+// flex frame — height IS auto, structurally
+{
+ "width_type": "SizeFixed",
+ "width": { "value": 400.0 },
+ "height_type": "SizeAuto",
+ "height": { }
+}
+ One policy owed for phase 3: re-encoding through an old schema
+ drops unknown fields — read-skip is free, read-modify-write
+ preservation is not.
+
+
+
+
+
+
+
exhibit e4 — the resolver, timed
+
10,000 nodes in 5.4 ms, unoptimized.
+
+
+
+
scene
+
nodes
+
full resolve
+
throughput
+
+
+
flat canvas (⅐ rotated)
+
10,001
+
5.42 ms
+
1,844 nodes/ms
+
+
+
flex cards — nested hug + text measure (worst case)
+
5,785
+
24.4 ms
+
237 nodes/ms
+
+
+
mixed groups + rotated-in-flow
+
10,003
+
8.21 ms
+
1,218 nodes/ms
+
+
+
one card subtree (locality bound per leaf edit)
+
6
+
18 µs
+
—
+
+
+
+
+ Single-threaded, zero caching, and the lab double-runs layout on hug
+ containers — this is the floor. Scaling is linear (10× nodes
+ → 9.6× time in the all-flex worst case). What it replaces: the
+ 26-arm branch forest, the atan2 transform reconstruction, and
+ MIN_SIZE_DIRTY_HACK.
+
+
+
+
+
exhibit e5 — a million transforms
+
The corpus re-ranked the escape hatch.
+
+ 7,138 SVGs, 1,003,787 transforms scanned. The lens was designed for skew
+ — but genuine shear is under 1%. The real finding is
+ flip at 26%: single-axis mirrors must be native header bits, or the
+ "quarantine" fires on a quarter of wild files.
+
open model-v2/a/e1-rotation-in-flow/demo.html # the offline twin of the demo above
+cd model-v2/a/lab
+cargo test # 56/56 conformance-derived tests
+cargo run --bin e1 # regenerate the sweep, frames, metrics
+cargo run --bin e3 -- truth # ground truth an agent must match
+cargo run --release --bin e4 # the timings above
+
+
+ Full narrative, amendments E-A1…E-A7, and the honest lose column:
+ model-v2/a/REPORT.md.
+ Nothing is committed to git — the workbench is yours to review.
+
+
+
+
+
diff --git a/model-v2/a/.preview/model.html b/model-v2/a/.preview/model.html
new file mode 100644
index 0000000000..312fef8f88
--- /dev/null
+++ b/model-v2/a/.preview/model.html
@@ -0,0 +1,1017 @@
+anchor — how the model works
+
+
+
+
+
+ model-v2 · anchor · model demo — 2026-07-07
+
+
One box. Content realizes into it.
+
+ The consolidated model in three interactive laws. A node is an
+ anchored box with typed content: the box comes from intent, content
+ is realized into the box at render, and nothing derivable is ever
+ stored. Everything below runs the spec’s own formulas (the same ones
+ the Rust lab asserts in 58 tests and LLMs predicted cold at 100%).
+
+
+
+
+
demo a — the two-axis core
+
One box, four realizations.
+
+ Drag the box size.
+ The document writes are the same two fields every time (w, h)
+ — what differs per kind is how content re-realizes: parametric
+ shapes re-derive, vectors re-map from their reference rect, text re-wraps,
+ image paint re-fits. No points move, no matrix appears, no bake happens.
+
+ Content re-wraps at the imposed width (greedy, the spec metric).
+ Font size stays 14 — a style, not geometry.
+
+
+
stored: content + size=14
+
+
+
image fill · fitted
+
+ Paint re-fits at the resolved box per fit mode — never
+ stretched with geometry.
+
+
+
+
stored: image ref + fit mode
+
+
+
+
+
+
+
demo b — the points law (e-a9)
+
Resize all you want. The blob stays the blob.
+
+ The table is the stored document data — vertices in the
+ reference rect. Resize writes w/h and the table never changes (Sketch
+ stores 0–1 points, Figma ships
+ normalizedSize ≠ size
+ nodes — same law). Only a vertex-editing gesture writes
+ points; then the reference bounds re-derive.
+
Resize is not scale. And scale is two different things.
+
+ One card (dotted image fill, 2px stroke, radius 10, label 12px), one
+ slider, three semantics. Watch the document under each: resize
+ writes two fields and keeps every style in px; K is a sanctioned
+ bake that multiplies the numbers; lens stores one op and touches no
+ value — the picture semantics (strokes and tiles magnify),
+ quarantined.
+
+
+
+
+
+
+
+
+
resize
+
+
+
+
+
K · parameter scale (bake)
+
+
+
+
+
lens · picture scale (stored op)
+
+
+
+
+
+ Law 5. Styles are px-stable under resize (non-scaling-stroke by
+ construction). K bakes numbers through the subtree — Figma
+ rescale(), Sketch K, Grida parametric_scale, already shipping. A
+ retained picture-scale exists only as a lens op — the
+ SVG/Graphics-container semantics, opt-in, never a prerequisite.
+
+
+
+
+
+
diff --git a/model-v2/a/COMPAT.md b/model-v2/a/COMPAT.md
new file mode 100644
index 0000000000..9861b21bd0
--- /dev/null
+++ b/model-v2/a/COMPAT.md
@@ -0,0 +1,174 @@
+# COMPAT — the import maps (css → grida · svg → grida · figma → grida)
+
+2026-07-07. The three import sources the model must be able to absorb,
+mapped primitive-by-primitive into the anchor vocabulary. This document
+is also a **standing design harness (H13)**: any change to the model
+must state its effect on these maps; a change that silently moves rows
+toward `unsupported` is a regression even if every internal test stays
+green.
+
+**Verdicts** — `native` (direct field map) · `surgery` (tree
+restructuring, semantics preserved) · `trick` (non-obvious but exact
+construction) · `lens` (quarantine wrap) · `paint` (lands in the paint
+model, not node geometry) · `degrade` (approximate, loss declared) ·
+`unsupported` (flatten/rasterize/drop) · `out-of-scope`.
+
+Full row data (33 CSS + 31 SVG + 30 Figma rows with per-row edge notes)
+was produced by the 2026-07-07 mapping run; the tables below are the
+complete verdict census with the sharpest edges inlined.
+
+---
+
+## 1. CSS → grida
+
+**Posture: the positioned/flex core is nearly the native tongue** —
+insets literally are the Pin/Span vocabulary (`right:24` = `Pin{End,24}`,
+`left+right` = `Span`) and flexbox maps 1:1 minus three declared holes.
+The systematic surgeries: block flow → flex column; margins →
+gap/padding/spacers; z-index → fractional-index resort. Anything scroll-
+or responsiveness-coupled freezes at the import viewport **or stays live
+in the htmlcss engine via the `embed` kind — the sanctioned pressure
+valve** (Grida ships a real CSS engine; import-to-canvas is a choice,
+not the only door).
+
+| primitive | verdict | map / sharpest edge |
+| ------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
+| position:absolute + insets | **native** | Pin/Span per edge; intent survives resize. Edge: CSS containing block ≠ direct parent → re-parent surgery; auto-inset static positions bake |
+| display:flex + direction/wrap | **native** | LayoutBehavior. Edge: no `*-reverse` (index-reverse surgery); no align-content; wrap-reverse unrepresentable |
+| justify-content | **native** | main_align 1:1. Edge: safe/left/right collapse |
+| align-items/self | **native** | cross_align/self_align. Edge: baseline = DEFER (X-CSS-9), bakes stale offsets |
+| flex-grow/basis | **native** | grow + SizeIntent. Edge: `flex:1` ⇒ basis 0 ⇒ write Fixed(0)+grow:1, not Auto |
+| flex-shrink | **degrade** | X-CSS-2 deviation (shrink:0); bake shrunk sizes at import viewport |
+| gap | **native** | direct. % gap bakes |
+| block flow / static | **surgery** | → flex column + stretch; margin collapsing must resolve _before_ conversion |
+| margin | **surgery** | gap/padding/spacers; negative margins → pins or lens (visibly wrong if flow relied on displacement) |
+| margin:auto | **trick** | Pin{Center}/alignment/grow-spacers |
+| padding | **native** | EdgeInsets; on non-frames → wrapper |
+| border + box-sizing | **paint** | strokes (border-box by construction); heterogeneous per-side borders degrade |
+| position:relative | **lens** | flow slot + lens Translate — exact CSS. Edge: containing-block duty doesn't transfer |
+| position:fixed | **surgery** | re-parent to viewport root; scroll immunity gone → embed for live |
+| position:sticky | **degrade** | freeze at scroll 0; embed for live |
+| display:grid | **degrade** | run grid once, bake cells to Pin/Span (mode:grid is the additive future); fr/minmax responsiveness lost; embed for live |
+| transform translate/scale | **lens** | ordered ops, exact CSS paint-only semantics; % translate bakes |
+| transform rotate (in flow) | **native** | DEC-0 flipped the default to visual-only: header rotation IS CSS rotate semantics now (lens-rotate ≡ header-rotate; X-CSS-5 fork dissolved) |
+| skew/matrix | **lens** | the quarantine's founding purpose (wrap, never degrade) |
+| 3D + perspective | **lens** | spec-reserved vocabulary; preserve-3d across elements doesn't compose (each lens flattens) |
+| transform-origin | **trick** | Alignment enum or Translate-conjugation (costs the one-op-one-channel story) |
+| overflow/clip | **native** | clips_content; scroll/auto degrade → embed for live panes |
+| z-index/stacking | **surgery** | resort fractional indices; cross-hierarchy interleaving (negative z, cousins) unrepresentable |
+| %-/vw/em units | **degrade** | bake to px. Exception: 100% → Span{0,0}/stretch — the one intent-preserving unit |
+| min/max/fit-content | **degrade** | X-CSS-3 DEFER; text Auto covers text only; min-content floor absent |
+| aspect-ratio + clamps | **native** | direct. Edge: anchor never overrides Fixed (CSS transferred-size cases diverge) |
+| display:none | **native** | active:false (excluded from measure — tested) |
+| display:contents | **surgery** | splice children up (group is NOT equivalent — it has a derived box) |
+| inline formatting (text+atoms) | **degrade** | pure text → text kind; atom interleaving inside wrapped lines is outside the model |
+| float/clear | **degrade** | bake positions; wrap-around needs exclusions we don't have |
+| writing-mode / rtl | **unsupported** | X-CSS-7 declared N; BiDi is text-internal only |
+| order | **surgery** | rewrite fractional indices; a11y/source order distinction erased |
+| tables | **degrade** | bake column widths into nested flex; shared column sizing has no home |
+
+## 2. SVG → grida
+
+**Posture: mostly native, with the paint model and usvg pre-processing
+absorbing what the node model shouldn't.** E5 already measured
+transforms (native 73% / flip 26% → E-A2 / true shear 0.95% → lens).
+The remaining edges cluster in four families: **viewBox proportional
+scaling** (SVG's scaling-stroke default is _our_ exotic case — the exact
+inversion of law 5), **instancing** (`use`/`symbol` = copy-on-import,
+divergence declared), **text** (baseline math, per-glyph positioning,
+textPath), and **paint servers** (`pattern` has no home yet).
+
+| primitive | verdict | map / sharpest edge |
+| --------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
+| root `