# Client runtime

Load this card when implementing or changing a Libertas client — Studio,
Android or iOS — not when writing an App. It holds the rules only a client
implementer acts on: scheduling, queue and dispatch order, pending and blocked
markers, removal mapping, reply application and refusal, report merging,
screen and landing bookkeeping, AI Search candidate assembly, validation
rollback and failure markers, initial-text priority, the client's side of AI
Import, task creation and access approval, the Matter device-descriptor
encoding and matching, draft storage and restoration, field-edit commitment,
identity-default resolution, enum/protocol highlighting, task-editor
candidate filling, and chart rendering: payload projection, scale and tick
resolution, guide and legend layout, the responsive overlay, hit testing,
level of detail, 3D camera and gesture handling, and chart accessibility. Its
rules are generated from the contracts that own them; App-authoring rules for
the same features are on the task cards the
[protocols/runtime router](protocols_runtime.md) lists, such as the
[calculation card](calculations.md), and on the [AI Import](ai_import.md),
[App design](app_design.md), [validation](validation.md),
[source documentation](source_documentation.md),
[data model](data_model.md), [chart](charts.md) and [3D chart](charts_3d.md) cards.

<!-- generated:codebot-rules task=client-runtime -->
<!-- Generated from the reference rule markers; edit the reference and regenerate. -->

## Rules to apply

Studio does not check these rules. Each is quoted from its owner.

### Draft scope and lifetime

Owner: [annotations.md#draft-scope-and-lifetime](../reference/annotations.md#draft-scope-and-lifetime)

- `draft.identity.initialize-before-lookup`: The full cache key comprises Hub identity, task ID, endpoint ID, request variant index, and the complete CacheIdentity tuple, within the authenticated account's storage. The first four components identify the page, not the entire cache key. Different workflow identities and revisions retain separate drafts. Initialize the page first, then look up its complete identity directly; unrelated entries do not need to be scanned or deleted. Garbage collection removes expired drafts.
- `cache.draft.expiry`: Entries expire one calendar month after their last committed update. Clients collect expired entries at startup and reject expired entries before restoration. Reads and restore prompts do not extend retention. Removing the Hub, task, or endpoint removes its drafts.
- `cache.draft.delete-after-reply`: Delete a submitted editor's draft only after its successful correlated backend reply. Transport acceptance alone is insufficient. Retain it on transport or backend error.
- `cache.draft.calculation-replies-keep`: Calculation replies never clear the parent draft.
- `cache.draft.go-back-and-close`: When offered under the [pane contract](../reference/presentation.md#master-detail-panes), the workflow's in-app **Go Back** action selects a declared PrevRequest and voluntarily abandons the current edit. Delete that editor's draft after the successful correlated, non-error reply to the previous action. Cancelling its chooser, failing validation, or receiving a transport or backend error retains the draft. An explicit editor **Close**, including Close in the right-hand master-detail pane, deletes that editor's draft when the close is accepted. These actions delete only the departing editor's matching draft; they do not delete a retained leading editor's draft or other workflow identities.
- `cache.draft.system-back-preserves`: System-level Back by button or gesture, ordinary screen-navigation Back, and process exit or interruption preserve the committed draft for recovery. They are separate from the workflow's Go Back action. Closing a field picker or nested editor returns to its parent and does not discard the parent's draft. The system Close of a [stacked-link page](../reference/protocols.md#links) likewise releases only that displayed page and preserves the live editor beneath; it is not an explicit editor Close. Navigating away from the stack follows these existing draft rules.

### Restoration and calculations

Owner: [annotations.md#restoration-and-calculations](../reference/annotations.md#restoration-and-calculations)

- `cache.restore.writable-values`: Restoration assigns only the values the user can edit and the destinations that a calculation or a link return writes; a link-return destination cannot be recomputed, so it is restored as saved. Every other Hidden, ReadOnly, or Fixed value, including a cookie and an allowed-set source that no calculation or link return writes, keeps the value the fresh editor supplied; the draft's copy of it is not applied. A restored selection outside the fresh allowed set becomes unanswered under the [reset rule](../reference/protocols.md#calculated-allowed-sets) and does not discard the draft.
- `cache.restore.atomic-compatibility`: Restore atomically against the current schema; a source that a calculation writes is restored from the draft and recomputed by the seeded calculations. Corrupt encoding or structural/type incompatibility discards the cache and rolls back the entire attempt to the fresh editor. Compatibility includes the declared primitive kind and representation, record fields, List element types and available Union branches; a value of the wrong kind or an invalid encoding is not a restorable draft. Do not partially restore a structurally incompatible draft, coerce incompatible data, or silently omit incompatible fields. An exact schema version match is not required.
- `cache.restore.local-reset`: For a structurally compatible draft, check each restored node's own value constraints, including numeric bounds evaluated in the current editor and reference context under the [range contract](../reference/validation-rules.md#31-dynamic-numeric-ranges). Replace an invalid value through ordinary initialization at that node, including its applicable Default or an unanswered value. A descendant's failure must not reset its containing record, List item or page; preserve every other restored value. A List that itself violates its size constraint is reset at that List. These local resets are part of the atomic restoration and do not discard the compatible draft.
- `draft.restore.normal-calculation-scheduler`: After successful restoration, seed calculations once for all current instances, including array items. Reset destinations through ordinary initialization and reevaluate using restored inputs. Use the normal ordered scheduler and dependency propagation; incomplete or invalid requests cannot be sent. Do not restore old in-flight transactions or automatically submit the workflow editor.

### General field-edit commitment

Owner: [annotations.md#general-field-edit-commitment](../reference/annotations.md#general-field-edit-commitment)

- `edit.commit.finished-only`: Only finished editing commits a user-entered field value. This applies throughout client editors, not just caching. Intermediate control text neither replaces the committed value nor triggers calculations or cache updates. Typing `-` in a number control therefore does not turn an existing committed number into undefined. An unassigned field remains undefined until a completed edit assigns an admissible value.
- `edit.commit.submission-resolves`: Submission must resolve the active edit through this same boundary; it cannot silently submit an old value while replacement text remains unfinished. Native controls may use their normal completion actions. Programmatic initialization and atomic calculation updates retain their transaction boundaries.

### MIME-aware Bytes in workflow drafts

Owner: [annotations.md#mime-aware-bytes-in-workflow-drafts](../reference/annotations.md#mime-aware-bytes-in-workflow-drafts)

- `cache.mime.draft-link`: For a [MIME-aware Bytes](../reference/schema.md#mime-aware-bytes) field, a draft never stores the media content. It stores a client-local file link when one exists; an in-memory value received from the server stores only a marker to use the freshly initialized value after recovery. An explicit clear stores an undefined marker. The link and its temporary file stay within the client and are never sent to the Hub. A missing or unreadable link on recovery makes only that field undefined and reports which field needs a new file; the other edits restore. If no corresponding fresh value exists for an in-memory marker, that field is undefined. This is an exception to full-value draft storage, not a relaxation of identity or schema checks.
- `cache.mime.temp-file-lifetime`: A temporary file remains while an active editor or unexpired draft refers to it and is automatically removed after its last reference and a cleanup grace period.

### Functions, permissions, and singleton tasks

Owner: [app-contract.md#functions-permissions-and-singleton-tasks](../reference/app-contract.md#functions-permissions-and-singleton-tasks)

- `tasks.creation.dependency-admission`: Before creating a task, Android and iOS first fail fast on a `Singleton` conflict, then queue the requested creation while statically checking required task-process dependencies. They traverse the complete effective input schema. A client Endpoint carrying `Default` or `Fixed` is absolutely required when neither it nor an ancestor is `Nullable` and no ancestor List permits zero items. Every such Endpoint needs a currently compatible server; `Fixed` also requires the exact named task-process identity.
- `app.task-creation.install-required-tasks`: Missing servers open the required-task installation UI, and the queued creation proceeds only after a fresh complete check finds every dependency satisfied. Cancelling any required dependency installation cancels the originating task creation. See the [task pre-creation dependency check](../reference/schema.md#62-task-pre-creation-static-dependency-check).
- `app.permissions.approval-view`: The client approval view always shows the raw permission key as its current summary and the requesting App's same-key localized `StringResources` value as a separate explanation.

### Task removal

Owner: [app-contract.md#task-removal](../reference/app-contract.md#task-removal)

- `tasks.removal.repeat-authorization`: The Hub repeats the same analysis immediately before executing removal. An earlier successful probe cannot bypass a later permission change. When the repeated check fails, including when the Hub cannot inspect the complete task configuration, the Hub reports the failure and does not begin removal.

### Client-side charting for Libertas protocol data

Owner: [2d.md#client-side-charting-for-libertas-protocol-data](../reference/charts/2d.md#client-side-charting-for-libertas-protocol-data)

- `chart.data.value-schema`: Presentation uses the schema, exact dependency versions, and locale document associated with the returned value; unrelated installed versions do not redefine that value. See the [schema reference](../reference/schema.md).

### 2.4 Client interpretation

Owner: [2d.md#24-client-interpretation](../reference/charts/2d.md#24-client-interpretation)

- `chart.data.spec-from-schema`: Android and iOS build the semantic `ChartSpec` from only the generated schema and decoded `ScriptValue` tree. The presentation layer may then decorate that model with the already-resolved localized document. A client must not infer chart behavior from document prose, source-language text, or Task Editor presentation rows.

### 3.1 Chart metadata belongs to returned data

Owner: [2d.md#31-chart-metadata-belongs-to-returned-data](../reference/charts/2d.md#31-chart-metadata-belongs-to-returned-data)

- `chart.data.payload-lookup`: The presentation path checks only the effective type of the `ScriptValue` selected as the response or subscription payload. If that effective value does not carry `Chart`, the ordinary data view is shown. A client must not recursively hunt for an arbitrary annotated descendant. For a Union response, the client first resolves the selected concrete variant and then checks that effective variant value.

### 4.1 Primitive marks

Owner: [2d.md#41-primitive-marks](../reference/charts/2d.md#41-primitive-marks)

- `chart.mark.bar-lowering`: `bar` is a convenience. A conforming implementation may lower it to `rect` before rendering. This keeps the primitive renderer small without making common source schemas verbose.

### 4.2 Compositions

Owner: [2d.md#42-compositions](../reference/charts/2d.md#42-compositions)

- `chart.compose.facet-grid-lowering`: Facet-grid equality in [resource limits](../reference/charts/2d.md#12-security-resource-and-accessibility-requirements) defines only a portable count; it does not merge independent scales or choose one layered child's labels. A renderer that lowers layered children into one physical facet grid may do so only when every facet orientation uses the same nonempty compatible shared scale ID and every resolved raw identity has the same display label. Otherwise it renders the grids and headings independently or follows the chart failure policy in §10. A faceted and an unfaceted child therefore do not silently become one grid.

### 5.3 Cartesian and polar geometry

Owner: [2d.md#53-cartesian-and-polar-geometry](../reference/charts/2d.md#53-cartesian-and-polar-geometry)

- `chart.geometry.lane-thickness`: For `bar` or `rect` with a continuous `x,x2` interval in a discrete `y` band, the categorical dimension uses a compact density-independent screen-space thickness capped at 72 percent of the mapped band. It is not synthesized by adding and subtracting category-index values. A positive interval shorter than that lane thickness may receive a paint-only minimum of the same length, while a zero interval remains one device pixel. A positive floor stays centered on the mapped source interval and clips at a plot edge; it must not shift inward, because doing so can collapse distinct adjacent buckets onto one identical footprint. A true zero at an edge may shift inward only enough to retain its single device pixel. Clipping, hit testing, and overlap use the same painted footprint. This responsive presentation never changes source endpoints, domains, tooltip values, semantic order, or stacking.
- `chart.geometry.synthetic-overlap`: The paint-only minimum is not authored geometry. Within one categorical lane, raw intervals with a positive intersection retain source/`order` precedence; a zero interval likewise retains that precedence when it is identical to or contained by another raw interval. Two positive intervals that merely abut do not overlap. If otherwise-disjoint raw intervals overlap only after clipping or minimum-width expansion, the renderer may adjust only their screen z-order so the containing or wider synthetic footprint paints first and the contained or narrower footprint remains visible. Equal synthetic footprints retain canonical order. Any conflict is resolved in favor of authored raw-overlap precedence. The resulting order and exact clipped footprint are shared by painting and hit-test ties, while appearance lookup continues to use the row's canonical source/`order` identity. Grid and axis strokes paint behind these marks so an edge-clipped one-device-pixel interval cannot be erased. No part of this screen-space rule changes scales, raw endpoints, lane assignment, tooltip values, keys, or stacking.
- `chart.geometry.polar-plot-circle`: after titles, guides, and facet headings reserve their space, the polar plot is the largest centered circle in the remaining pane. Its physical radius `R` is half the smaller plot dimension, so a polar plot is never stretched into an ellipse;
- `chart.geometry.polar-projection`: for normalized mapped values `t,r`, the screen coordinate is `x = centerX + R * r * sin(2*pi*t)` and `y = centerY - R * r * cos(2*pi*t)`. Scale reversal is applied before this conversion. A polar layer uses this same center and `R` for every child;
- `chart.geometry.polar-clip`: after valid geometry is constructed, marks and hit regions are clipped to the circular plot boundary. Automatic guide labels and titles occupy their reserved guide space and are not part of that mark clip. Painting and hit testing use the same final clipped footprint;

### 5.4 Default order and grouping

Owner: [2d.md#54-default-order-and-grouping](../reference/charts/2d.md#54-default-order-and-grouping)

- `chart.row.numeric-identity`: Numeric raw identity is also closed. An integral Number retains its exact signed or unsigned typed integer. An `f32` is first rounded to its declared binary32 value and then widened exactly to binary64; an `f64` retains its binary64 value. Floating negative zero is canonicalized to positive zero before grouping, ordering, key comparison, style constancy, or candidate deduplication. A nonfinite value is invalid before it can become an identity. For a shared numeric scale, every contribution is converted to binary64 with round-to-nearest, ties-to-even; equal converted mathematical values occupy one domain coordinate even when the original integer widths differ. That geometry deduplication never merges source rows or rewrites their exact raw tooltip, key, or request values.

### 5.5.1 Physical units

Owner: [2d.md#551-physical-units](../reference/charts/2d.md#551-physical-units)

- `chart.units.unsupported-preserve-value`: Schema admission and client rendering support are intentionally separate. The schema always preserves a valid CLDR identifier because that semantic metadata is useful even when a particular client ICU version cannot format it. A client that supports the identifier localizes the complete unit, including a compound numerator and denominator. A client that does not support it may display the numeric value without unit decoration; it must not reject or alter the value.
- `chart.unit.client-formatting`: A client that can format a unit-bearing visual number first applies that surface's existing deterministic numeric precision rule, then asks its CLDR unit formatter to join the resulting number and the declared *provided* unit. Visible chart and ordinary-data text uses CLDR short width; accessibility uses CLDR long width with the locale's plural and grammatical rules. A client must not concatenate an English name or ASCII symbol, infer a unit from a native field name or description, convert to a locale-preferred unit, or let the unit formatter independently change the already selected numeric precision. Negative zero remains zero.

### 5.6 Appearance values

Owner: [2d.md#56-appearance-values](../reference/charts/2d.md#56-appearance-values)

- `chart.appearance.zero-stroke`: A resolved stroke width less than or equal to zero paints no path or boundary, creates no stroke hit region, and must skip the native stroke call. Clients must not pass semantic zero to Core Graphics, Skia, or another backend that may interpret zero as a device hairline.
- `chart.appearance.polygon-paint-order`: The renderer fills each compound path with `evenOdd`. Within one unit it paints every polygon detail-group fill in group paint order before painting any of the groups' explicit visible boundary strokes in that same order. A later translucent fill therefore cannot bury an earlier series outline. Each stroke remains above every fill along every subpath boundary, including its implicit closing edge.
- `chart.palette.theme-contrast`: The base hue and category index are semantic and remain stable. At rendering time, each client minimally adjusts an automatic categorical color toward the platform theme's semantic foreground when the base color does not provide at least `3:1` non-text graphical contrast against every active opaque chart surface. This adaptation may differ between native platform themes, but it must not reorder categories, rotate hues between categories, use localized labels as identity, or change while a theme remains unchanged. Legends and marks use the same adjusted color. Color is never the only category indicator: resolved legend text and accessibility semantics remain authoritative.
- `chart.palette.viridis`: A continuous numeric paint scale without `mid` uses these Viridis stops from domain minimum to maximum:

  ```text
  #440154,#482878,#3E4989,#31688E,#26828E,
  #1F9E89,#35B779,#6DCD59,#B4DE2C,#FDE725
  ```

- `chart.palette.interpolation`: Clients interpolate unpremultiplied sRGB components linearly between adjacent stops. These are semantic rendering constants, not extra schema attributes. They make heatmaps, divergent maps, and legends deterministic on both platforms.
- `chart.appearance.size-saturation`: A client may saturate an accepted size only while converting it to the finite native scalar range of its drawing API. Such representational saturation is not schema validation and must never reject the chart.
- `chart.appearance.ordinal-output`: Every ordinal appearance output is also deterministic. Let `i` be a category's zero-based index in a domain of `N` values. Ordinal paint uses palette entry `i`; shape and stroke dash use entry `i` in their fixed lists. For opacity, size, and stroke width, let `u=0` when `N=1`, otherwise `u=i/(N-1)`, and map that value through the same bounded physical range as the corresponding continuous scale. Ordinal angle instead uses the half-open slot `360*i/N` degrees, with a singleton at zero, so the last category never duplicates the first at 360 degrees. `reverse=true` replaces `i` with `N-1-i` for output selection without changing raw domain order. Paint and all other ordinal appearance domains are limited to ten categories unless the smaller shape or dash limit applies. A schema-integral, non-`TimeInterval` Number may opt into this ordinal behavior; a floating or duration Number may not use coincidental whole-valued rows as categories.

### 5.7 Runtime row semantics

Owner: [2d.md#57-runtime-row-semantics](../reference/charts/2d.md#57-runtime-row-semantics)

- `chart.row.hit-priority`: Layer traversal and row order define hit priority: the last painted eligible mark wins when distances are equal. A line segment resolves to the nearest of its originating endpoint rows. Tooltip and request actions use that row's original `ScriptValue`; the renderer never synthesizes or joins a source row.

### 6.1 Properties

Owner: [2d.md#61-properties](../reference/charts/2d.md#61-properties)

- `chart.scale.transform-geometry`: Continuous scale geometry is defined in transformed coordinates so every client produces the same mapping instead of delegating nonlinear semantics to a platform library. For a finite raw value `v`, the canonical transform is:

  ```text
  linear, time, utc:  f(v) = v
  log:                f(v) = ln(v)                         (v > 0)
  symlog:             f(v) = sign(v) * ln(1 + abs(v))
  sqrt:               f(v) = sqrt(v)                       (v >= 0)
  ```

- `chart.scale.date-only-ordinal`: `DateOnly` is the explicit temporal exception to the first line. Its raw identity remains the integer `yyyyMMdd`, but `time` and `utc` decode it to the timezone-free proleptic-Gregorian day ordinal relative to 1970-01-01 before mapping: `f(v)=days(date(v))-days(1970-01-01)`. The mathematical inverse is a real day ordinal; only an exact integral ordinal is a typed DateOnly candidate and encodes back to `yyyyMMdd`. Clients never round a fractional ordinal or interpolate through invalid decimal dates when producing a data value, tick, or interaction value; §9 alone rounds a transient requested viewport boundary to a typed date.
- `chart.scale.normalization`: The `symlog` transform therefore uses the fixed portable constant `1` inside `log1p`; version one does not expose a configurable constant. Every canonical transform and inverse result is rounded to binary64 with round-to-nearest, ties-to-even; clients use a shared correctly-rounded implementation or match the conformance vectors rather than accepting a platform-libm ULP difference. For a resolved raw domain `lo..hi`, let `A=f(lo)`, `B=f(hi)`, `T=f(v)`, and `q=max(1,abs(A),abs(B))`. Clients use the overflow-safe normalization `u = ((T/q) - (A/q)) / ((B/q) - (A/q))`. The contract value is the round-to-nearest, ties-to-even binary64 rounding of that mathematical real ratio; a conforming implementation uses sufficient intermediate precision if binary64 division would collapse distinct finite `A` and `B`. `reverse=true` maps the result to `1-u`. An implementation compares `T` with the ordered transformed endpoints before normalization; an out-of-domain value is classified beyond the corresponding output edge without evaluating a potentially overflowing outside ratio.
- `chart.scale.inverse-transform`: Guide candidates and responsive density follow the finite algorithm in §6.4.5; a nonlinear tick position is always computed through this transform and its label represents the corresponding raw value. Interaction performs the matching inverse transform: `exp(t)` for `log`, `sign(t) * expm1(abs(t))` for `symlog`, and `t * t` for `sqrt`. Implementations use the numerically stable `log1p` and `expm1` operations so values close to zero do not collapse during a forward/inverse round trip.
- `chart.scale.appearance-normalization`: The same transformed normalization governs continuous numeric appearance channels. Without `mid`, `u` selects the sequential range. With `mid`, the resolved raw domain expands to contain the midpoint, `lo..mid` maps through `f` to `0..0.5`, and `mid..hi` maps through `f` to `0.5..1`; the neutral divergent color is therefore exactly at `mid`. `reverse=true` is applied after that piecewise normalization. Appearance output clamps an out-of-domain value to the nearest range endpoint; its raw value remains unchanged in tooltips and accessibility. Appearance guides label raw values.

### Normative broken-time interval-lane policy

Owner: [2d.md#normative-broken-time-interval-lane-policy](../reference/charts/2d.md#normative-broken-time-interval-lane-policy)

- `chart.lane.span-guide-grouping`: The explicit `ChartGuide` is a generic span-label guide; `DateOnly` has no special guide status. Within each facet pane it groups eligible rows by the exact raw `bucket_starts_on` identity, maps and clips their authored `x,x2` intervals, merges overlapping intervals, and places one localized date at each connected component's midpoint. Its explicit guide replaces the implicit x axis. Adding, removing, or reordering tooltip fields therefore cannot change the guide. This is not a time scale: the client does not derive calendar distance from the labels, move an interval, fill a gap, or expose the synthetic coordinates.

### 6.2.2 Domain contributors and baselines

Owner: [2d.md#622-domain-contributors-and-baselines](../reference/charts/2d.md#622-domain-contributors-and-baselines)

- `chart.domain.contributors`: Every finite point, line, text, or polygon vertex contributes its primary position values to automatic domains before path lowering. A polygon source separator whose required position is null or non-finite contributes neither a position nor an appearance value. A polar polygon vertex whose finite raw position maps outside `0...1` becomes a separator only after domain resolution: its raw position still contributes to that resolution, but its appearance values do not contribute to appearance domains or group-style constancy. A valid secondary endpoint on a Cartesian or polar rule contributes through its primary scale; when a polar rule omits one secondary coordinate, repeating the primary coordinate adds no distinct domain value. A short or fully transparent polygon subpath therefore does not silently change how its finite authored values participate in an otherwise visible shared guide.

### 6.4.2 Ownership, layers, facets, and composition

Owner: [2d.md#642-ownership-layers-facets-and-composition](../reference/charts/2d.md#642-ownership-layers-facets-and-composition)

- `chart.guide.owner-key`: Guide sharing, suppression, and `position=auto` resolve before layout. Every surviving guide has an owner key `(panel,target,unit)`: `panel` is its concat pane, `target` uses this fixed guide-layout rank: `xOffset,x,yOffset,y,theta,radius,color,fill,stroke,opacity,fillOpacity,` `strokeOpacity,size,shape,angle,strokeWidth,strokeDash,row,column`. Offset tiers precede their parent axes so subgroup labels remain nearest the marks, and facet headers follow positional and appearance guides so their grid-wide title stays outermost. `unit` is depth-first flattened chart traversal order inside the pane and breaks ties between guides with the same target rank. A shared-scale guide uses the unit and target carrying its sole explicit declaration, or the first binding when all declarations are implicit. A value or span guide uses its declaring unit and target rank, never the guide-label field's source-field position.
- `chart.guide.concat-instances`: Each concat child owns its guide instances and layout even when its scale domain shares an ID with a sibling; concat sharing never deduplicates guides across panels. A faceted positional axis repeats in each pane because each pane owns a pixel range. A facet header appears once per resolved row or column lane, and an appearance legend appears once for the unit's complete facet grid. Layered facet grids share axes, headers, or legends only when the grid-equality and shared-scale conditions in Section 4.2 hold; otherwise each grid renders its guides independently.
- `chart.guide.a11y-order`: Accessibility visits outside sides in the fixed order top, right, bottom, left, using inward-to-outward guide order on each side, then intrinsic polar theta and radius guides in that order, then marks. Concat panes and facet panes retain schema traversal order within those stages.

### 6.4.3 Cartesian, offset, and facet guide geometry

Owner: [2d.md#643-cartesian-offset-and-facet-guide-geometry](../reference/charts/2d.md#643-cartesian-offset-and-facet-guide-geometry)

- `chart.guide.cartesian-geometry`: An x guide's domain is the plot's top or bottom edge selected by `position`; its ticks point outward and labels are unrotated and horizontally centered on their mapped coordinates. The nearest painted edge of an outside x label begins beyond the tick endpoint with a four-logical-point gap; multiline labels grow away from the plot edge and never back across the domain or grid. A y guide uses the selected left or right edge; its ticks point outward and labels are unrotated and vertically centered. For either family, a grid component crosses the complete plot perpendicular to the axis at each selected candidate. Scale reversal changes mapped candidate positions, never these physical anchors. Cartesian `source=value` and `source=span` guides use the same domain, tick, label, grid, and title geometry.
- `chart.guide.axis-title-placement`: A Cartesian axis title is complete localized schema context, not changing chart data, and therefore has zero horizontal plot authority. It may use otherwise-unused space already reserved by its guide, including a rotated side or corner placement, provided it adds no left/right inset and overlaps neither guide values nor marks. Otherwise the renderer places it in scrollable safe content above or below the complete plot-and-guide frame: a top or left guide uses the upper rail and a bottom or right guide uses the lower rail. A safe-rail title is unabridged. Titles in the same rail share a centered line in guide order, separated by a middle dot (` · `), and wrap using ordinary locale-aware text layout only when the combined text exceeds the available width. Their height extends the outer chart scroll content and never reduces the assigned charting-region height. Rotation, alignment, and opportunistic placement within already-reserved guide space are renderer-flexible and have no effect on scale, mark, hit-testing, or accessibility semantics. An axis title alone can never cause chart presentation failure.
- `chart.guide.offset-geometry`: An xOffset guide repeats its complete resolved child-domain tier inside every parent x band; yOffset does the same inside every parent y band. Each child tick and label is centered on its nested band. The repeated domain segment and grid separators are clipped to that one parent band. The offset title appears once and follows the same zero-horizontal-authority title policy above. Offset tiers are inside their parent axes by the layout rank in §6.4.2; they never create an independent full-plot scale line.
- `chart.guide.facet-geometry`: A column facet label is centered over or under each nonempty column lane. An automatic row facet label is centered above its row in a single-column stack; other row facet labels are vertically centered beside their row. Facet labels are changing data and retain their allocated header space. The facet field title is stable schema context and has the same zero-horizontal-authority policy as an axis title: it may use already-reserved guide or corner space, including a rotated placement, only when that adds no left/right inset and overlaps neither labels nor marks. Otherwise it appears once in the upper safe rail for a top or left facet guide, or the lower safe rail for a bottom or right facet guide. It may wrap there and extend only the outer vertical scroll content. A facet title can never reduce a facet pane, create trailing horizontal blank space, or cause chart presentation failure. Facet labels remain unrotated. Explicit `labels=none` or `title=none` suppresses that component; collision handling never silently removes an enabled nonempty lane label.

### 6.4.4 Polar guide geometry

Owner: [2d.md#644-polar-guide-geometry](../reference/charts/2d.md#644-polar-guide-geometry)

- `chart.guide.theta-geometry`: Let `P(t,r)` denote the normalized screen mapping in §5.3. A theta guide's `domain=auto` is the circle through `P(t,1)`. For each retained theta candidate, `grid=auto` draws the center-to-`P(t,1)` spoke, `ticks=auto` draws a short outward radial tick beginning at `P(t,1)`, and `labels=auto` places an unrotated label beyond that tick on the same ray. The label box lies wholly outward: its horizontal alignment follows the sign of the ray's x component and its vertical alignment follows the sign of its y component, with centered alignment on a zero component. The theta title is horizontally centered below the complete circle-and-label envelope.
- `chart.guide.radius-geometry`: A radius guide's `domain=auto` is the `t=0` ray from `P(0,0)` through `P(0,1)`. For each retained radius candidate, `grid=auto` draws its concentric circle except at radius zero, `ticks=auto` draws a screen-horizontal tick crossing the zero-angle ray, and `labels=auto` places an unrotated label on the ray's clockwise, screen-right side. The radius title is horizontally centered above the complete circle-and-label envelope. These same anchors govern theta and radius `source=value` guides. Theme-native font metrics, tick length, and gaps may differ, but the semantic anchors, orientations, and component ownership may not.
- `chart.guide.polar-coincident`: Mapped theta zero and one are visually coincident. When both survive candidate generation, they remain distinct raw accessibility entries, but the renderer paints one spoke and tick and displays the mapped-zero label; if no mapped-zero candidate exists, it displays the first coincident candidate in semantic order. A radius-zero candidate likewise retains its tick, label, and accessibility entry but has no visible zero-radius grid circle. Polar guide geometry is inert, outside mark hit testing, and not clipped by the circular mark clip. Collision rules may thin labels only at these anchors; they may not rotate or relocate them.

### 6.4.5 Scale ticks, labels, and typed formatting

Owner: [2d.md#645-scale-ticks-labels-and-typed-formatting](../reference/charts/2d.md#645-scale-ticks-labels-and-typed-formatting)

- `chart.ticks.quantitative-ladder`: Tick generation creates a deterministic ladder of raw candidate sets before measuring text. For this subsection, let the guide-cadence transform `g` equal `log10` with inverse `10^t` for a `log` scale and otherwise equal the canonical scale transform `f` and its inverse from §6.1. This guide-only decimal basis does not change normalized log positions, but it makes aligned log ticks decimal decades instead of raw `exp(n)` values. For a nondegenerate continuous quantitative domain, let `lo` and `hi` be the raw bounds of the fixed cadence-planning window defined below, let `a=min(g(lo),g(hi))`, `b=max(g(lo),g(hi))`, and let `P` contain zero when it is in-domain and inside that window, plus an authored `mid` when it is inside that window. Let `E` contain each resolved raw domain endpoint that is inside the planning window. `E` bounds candidate generation but is not added to an ordinary candidate tier: a non-aligned endpoint may carry a binary arithmetic tail and must not displace a denser aligned interval merely because it bounds the data. An endpoint appears in an ordinary tier only when the lattice itself generates that exact binary64 value. A transient viewport edge is not a data or scale-domain endpoint and is never synthesized as a tick. Candidate spacings are the strictly increasing set `S={1,2,5}*10^k` for every integral `k`; each mathematical spacing is rounded once to binary64, and duplicate, zero, or nonfinite results are omitted. For one spacing `s`, the candidate set is
- `chart.ticks.candidate-count-bound`: The client canonicalizes negative zero to zero, deduplicates by binary64 raw equality, and sorts by mapped output position and then raw value. Before converting `ceil(a/s)` or `floor(b/s)` to an integer or iterating, it computes the inclusive count with saturating arithmetic capped at `4097`; a nonfinite quotient, an index outside the exactly representable integer interval `-(2^53-1)..(2^53-1)`, or a count over `4096` skips that spacing without allocation. If the binary64 width `b-a` is nonfinite, or no positive representable member of `S` has a bounded count, the client goes directly to the terminal `P union E` tier. Otherwise it starts with the smallest bounded `s`, tests successive representable spacings, and stops after the last spacing not greater than the finite width or before the next spacing would be nonfinite. It then tests one terminal tier containing `P union E`. A nondegenerate ordinary tier must contain at least two retained candidates; a one-tick tier cannot win over the terminal endpoint pair merely because it fits. Cadence probing is a pure arithmetic and geometry operation. For a horizontal guide, the first bounded tier whose adjacent mapped anchors are at least `64` density-independent points apart wins. For a vertical or radial guide, the required pitch is the greater of `40` density-independent points and `1.4` times the effective caption-font line height. When `labels=none`, every orientation instead uses a `40`-point pitch. No candidate label is needed to make this decision. A degenerate domain has only its sole raw value. If even the terminal tier cannot fit, guide layout falls back as defined below. Candidate tiers are transient; only the selected tier counts toward guide resource limits. Normalized positions always use the canonical forward transform, so this one finite algorithm covers linear, log, symlog, and sqrt scales rather than delegating ticks to a platform library. For example, a compact linear domain `0...10.199999999999...` selects the aligned tier `0,5,10`; it does not force the non-aligned maximum into that tier and then collapse to two endpoint labels. The raw domain and mark geometry remain unchanged.
- `chart.ticks.temporal-ladder`: DateTime and DateOnly guides use the same `4096`-candidate bound, fixed-pitch capacity, resolved-domain-endpoint priority, and endpoint-only terminal tier. Their ordered cadence ladder is: `1,2,5,10,20,50,100,200,500` milliseconds; `1,2,5,10,15,30` seconds; `1,2,5,10,15,30` minutes; `1,2,3,6,12` hours; `1,2,7` days; `1,2` weeks; `1,3,6` months; then `1,2,5 * 10^k` years for integral `k >= 0`. A cadence producing more than `4096` candidates is detected with a saturating count capped at `4097` and skipped without allocating them. The cadence probe compares raw width `w` with the closed ladder using binary64 arithmetic. For nominal step width `d`, its probe count is `ceil(w/d)+1`; the tier fits capacity when this finite count is from `2` through the fixed-pitch capacity inclusive. Fixed cadences use their exact seconds. DateTime calendar cadences use `86400` seconds per day, seven days per week, `28` days per month, and `365` days per year. DateOnly uses the same day counts in its raw day-ordinal space. These nominal widths are probe geometry only; they do not redefine Gregorian boundaries. The probe does not create a calendar, enumerate civil boundaries, resolve a time zone, or call ICU. Exact UTC or local calendar candidates are enumerated only once after the cadence has been selected, while constructing the bounded visible guide run. DST and calendar variation may make that exact run denser; the selected-run stride rule handles it without changing cadence. Calendar conversion there is confined to the chart DateTime range and civil years defined in §5.5. Year cadences stop after testing the first step greater than the maximum of one, the absolute endpoint years, and their year span, or before the next step would overflow; no later step can add a new interior aligned year. The endpoint-only tier follows immediately, so the search is finite for every supported civil domain.
- `chart.ticks.temporal-alignment`: For `utc`, sub-day boundaries align in UTC; Gregorian day ordinals are anchored at 1970-01-01, week ordinals at Monday 1970-01-05, month indices use `12*year+(month-1)`, and an aligned year is divisible by its cadence. For `time`, the same divisibility rules apply to local civil fields. Every aligned local civil boundary maps to all valid instants: a daylight-saving gap is skipped and both instants in an overlap are retained. `DateOnly` tests only the day, week, month, and year cadences against its timezone-free Gregorian day ordinal; `time` and `utc` are identical for it and never synthesize sub-day candidates. `TimeOnly` tests only the millisecond through hour cadences aligned from raw second zero at midnight, plus each resolved-domain endpoint inside the planning window. A Number carrying `TimeInterval` is not a calendar: it uses the quantitative lattice in raw seconds and the existing localized duration formatter.
- `chart.ticks.time-only-end-of-day`: The TimeOnly bound sentinel `86400` never formats as the same midnight label as raw zero. It uses the client's localized end-of-day label with a locale-shaped `24:00` representation; if a locale does not conventionally display hour 24, the localized end-of-day phrase remains visible and distinct. The sentinel is not a row identity and cannot appear in a tooltip or data-anchored value guide.
- `chart.ticks.temporal-skeletons`: Temporal label skeletons follow the selected cadence: sub-second and second tiers include seconds and needed fractional digits; minute/hour tiers use localized time; day/week tiers use localized date; month tiers use month and year; and year tiers use year. A sub-day DateTime guide adds a localized date tier at the first visible candidate and every local or UTC day transition. Repeated local clocks add the localized numeric offset or zone abbreviation. If two retained temporal values still render identically, clients add omitted calendar fields, then fractional seconds, then a locale-shaped shortest round-tripping raw-seconds suffix until the labels differ.
- `chart.ticks.collision-thinning`: Discrete positional axes, nested offset tiers, and value/span guide rails begin with every resolved candidate in canonical domain or mapped-position order. Collision thinning tests strides `1,2,4,8,...` anchored at index zero and always retains the last candidate; the first non-overlapping stride wins. A categorical label may wrap to two lines before thinning. To choose that wrapping, enumerate every legal Unicode line-break position in the unabridged label, trim break whitespace from the two painted lines, and measure each resulting pair. Choose the pair that minimizes the greater of the two measured line widths; an exact tie chooses the earliest source break. If there is no legal interior break, the label remains one unabridged line. This same intrinsic two-line rule applies to categorical axes, value/span rails, facet labels, and categorical legend-entry labels; it never changes raw identity or accessibility text. On Android, automatic row labels above the panes of a single-column facet stack instead use the full pane width: retain the original label and wrap only when that width requires it. These headings add their measured height to the vertical stack without reserving side width or reducing plot height. Side and multi-column facet headers retain the intrinsic two-line rule. Facet headers and categorical legends never thin: every nonempty facet lane keeps its header and every legend keeps every resolved domain entry. They obey the fixed geometry in §6.4.3 and §6.4.7, and the chart falls back if all entries cannot remain visibly represented.
- `chart.ticks.visible-run-measurement`: After cadence selection, the client constructs and localizes exactly the one bounded visible run. It measures that run's final localized, unabridged text with at least four density-independent points of separation and applies the anchored stride-thinning rule when necessary. Thinning may remove ticks and their grid lines from that visible run; it must not change cadence, inspect a second cadence tier, or cause another cadence probe. Native font metrics may therefore thin a selected run differently, but may not select a different cadence. Ellipsis is a last layout fallback and never changes accessibility text.
- `chart.guide-label.numeric-precision`: An ordinary numeric guide candidate is presentation text, not a lossless wire serialization.
- `chart.guide-label.numeric-cadence`: Otherwise the complete retained numeric run owns one display cadence: an authored positive `tickStep` when present, or the quantitative cadence selected by the automatic ladder. For every run, the client also considers the smallest positive adjacent raw gap so candidates closer together than the selected cadence contribute the needed fractional scale. It first normalizes that gap to two significant decimal digits, removing any trailing zeros, so the gap's base-ten order does not inherit a long binary subtraction tail. Starting precision is the greater of the cadence-derived and normalized-gap-derived fractional scales. A value/span run with no positive gap uses its associated scale cadence, while a true singleton with neither uses the locale-shaped shortest round-tripping decimal.
- `chart.guide-label.precision-rounding`: For a positive decimal cadence `q`, remove trailing fractional decimal zeros and let `p` be the remaining number of digits to the right of the decimal point, clamped to zero; an integer cadence such as `100` therefore has `p=0` and never rounds away integer digits. The normalized gap's contribution to `p` is likewise its nonnegative decimal scale after the two-significant-digit rounding and trailing-zero removal above: `0.1` contributes one fractional place, while `0.25` contributes two. Round every finite binary64 candidate in the complete run to `p` fractional decimal places using round-to-nearest, ties-to-even, remove insignificant trailing fractional zeros, and then apply the locale's digit shaping, sign, decimal/grouping, and exponent symbols plus any existing semantic unit decoration. After rounding, fixed notation is required when the adjusted base-ten exponent is from `-6` through `20` inclusive; scientific notation is required outside that interval. This cadence-derived guard removes binary arithmetic tails such as `0.6000000000000001` from an otherwise ordinary `0.6` guide tick without inventing unrelated precision. If distinct retained identities collide, raise `p` by one for the whole run and retry, for at most 17 increases; the bounded terminal fallback is the locale-shaped shortest round-tripping decimal. A client never chooses precision independently per tick. Negative zero is the one canonical zero identity and displays as zero. DateOnly, DateTime, TimeOnly, and duration values use the semantic rules above; a duration collision is disambiguated with the locale-shaped shortest round-tripping raw seconds. For a `PhysicalUnit` Number, selected-visible-run collision testing and label measurement use the complete CLDR-formatted number-plus-unit strings at the selected precision; the unit formatter cannot introduce a second rounding pass. Visual text uses the short unit width and accessibility uses the long width defined in §5.5.1.

### Stable cadence during pan and zoom

Owner: [2d.md#stable-cadence-during-pan-and-zoom](../reference/charts/2d.md#stable-cadence-during-pan-and-zoom)

- `chart.cadence.inputs`: The selected guide spacing depends on the chart snapshot, resolved guide and scale/domain/transform, committed viewport span, settled plot length, guide presentation, component switches, and font-line-height/density metrics. It is independent of pan offset/history, locale, calendar/time zone, selection, tooltips, pointer position, animation, and mark paint state.
- `chart.cadence.pan-zoom`: Panning retains that cadence and alignment. Only aligned candidates and visible priority candidates appear; moving viewport endpoints are not invented ticks. Wider localized labels use the existing anchored thinning rule rather than changing cadence. Marks and guides move together. Live pinch transforms the current presentation; committing a new zoom span selects the new cadence. Gesture completion always commits the exact terminal viewport. Shared physical facet/layer guides retain the same cadence. No interaction changes raw data, source domains, or localization meaning.

### 6.4.6 Value and span guide sources

Owner: [2d.md#646-value-and-span-guide-sources](../reference/charts/2d.md#646-value-and-span-guide-sources)

- `chart.guide-label.span-accessibility`: Each emitted span entry exposes its localized label plus the inverse-mapped raw bounds of that visible clipped connected component, ordered in the target scale's input direction. It does not announce the unclipped authored bounds or an arbitrary contributing row. Original row intervals remain available only through their ordinary data and tooltip presentation. A span guide with no nonempty visible connected component resolves no guide instance.

### 6.4.7 Legend lowering and layout

Owner: [2d.md#647-legend-lowering-and-layout](../reference/charts/2d.md#647-legend-lowering-and-layout)

- `chart.legend.categorical-samples`: Categorical legends contain one entry per resolved domain value in mapped output order, including `reverse`. Sample geometry is closed and independent of runtime text: `color` and `fill` use a filled swatch; `stroke`, `strokeDash`, and `strokeWidth` use a line; `shape` and `size` use a point symbol; `angle` uses an asymmetric directional needle; `opacity` and `fillOpacity` use a filled swatch over the chart surface; and `strokeOpacity` uses a line over that surface. A text mark's size legend instead uses the localized client resource `ChartTextSizeSample` rendered at the mapped font size; the resource selects a representative glyph appropriate to the active writing system. Each sample uses the same resolved appearance output as its marks for the guide's target channel. A legend sample isolates that target: every non-target appearance component uses the accessible theme-native default for the owning mark and never borrows a value from an arbitrary row or another data-driven binding. Thus a point-size guide uses the default point shape and paint, a shape guide uses the default point radius and paint, and a stroke-width guide uses the default solid, fully opaque semantic foreground stroke. This isolation changes only sample presentation; it never changes the mark's combined appearance.
- `chart.legend.continuous-ramps`: Continuous `color`, `fill`, and `stroke` guides use the contract's portable color ramp. `opacity` and `fillOpacity` use a filled alpha ramp; `strokeOpacity` uses a line alpha ramp. A point-size guide places the owning point shape at every selected candidate, a text-size guide places the localized sample above at every candidate, an angle guide places an asymmetric needle at every candidate, and a stroke-width guide places a line at every candidate. Those samples use the mapped physical radius, font size, clockwise angle, or line width. The continuous rail's scale-output axis is exactly `160` logical points long in both orientations; Android uses density-independent points and iOS uses points. Cross-axis swatch thickness, tick length, and component gaps remain theme-native and measured, but cannot alter that 160-point mapping. Their selected raw candidates provide ticks and labels. The sample or rail is intrinsic and remains present independently of `title` and `labels` component switches; accessibility still exposes its target role and unabridged raw domain.
- `chart.legend.packing`: Every left/right categorical legend is one vertical entry column ordered top to bottom. An above/below categorical legend uses stable greedy row packing: measure every complete entry with the final localized text and accessibility font, retain domain order, place the longest prefix whose entry widths plus fixed `8`-point inter-entry gaps fit the available width, then repeat on the next row. At least one entry is attempted per row; an individual entry wider than the available width is an essential-label layout failure. Entries never split, clip, reorder, or use balancing/look-ahead. After packing, the widest produced row defines the intrinsic width of the complete above/below guide group. Center that group within the available chart width, then retain one shared leading edge for every row inside it; shorter rows are not centered independently. Multiple blocks on the left or right form outward-stacked columns. Multiple blocks above or below share a second stable greedy horizontal flow instead of each claiming a complete row. First measure each block at its ideal unwrapped width; if that exceeds the viewport width, remeasure that block at the viewport width using its internal entry flow above. In resolved guide order, place the longest prefix of complete blocks whose widths plus fixed `16`-point inter-block gaps fit, then continue on the next row. A block title remains attached above its entries, so two compact blocks such as `Line` and `Result` may sit head-to-head on one row without mixing their entry identities. There is no balancing or look-ahead at either packing level. Extra rows increase the chart's outer scroll height instead of consuming plot width or creating a private legend scroller. A vertical continuous rail maps normalized scale output zero at the bottom and one at the top; a horizontal rail maps zero at the left and one at the right. `reverse` therefore moves raw values without changing the legend geometry. A visible categorical or vertical-rail title sits above its legend; a horizontal-rail title sits at its screen-left leading edge. It precedes entries in accessibility order. Entries never reorder, wrap into another row or column, or disappear. Labels within one entry may use the two-line categorical wrapping rule. A legend must fit its non-scrolling cross-axis: top/bottom legends must fit the chart viewport width, while a left/right legend that prevents the minimum plot width joins the deterministic temporary overlay under §6.4.8. A legend may exceed the visible viewport vertically under §6.4.8; that is ordinary outer-scroll overflow, not layout failure. There is no legend-only scroller, entry clipping, semantic thinning, or maximum legend height.
- `chart.legend.entry-density`: Categorical entry density is portable rather than platform-native. Entry labels use the client's 11-point small/caption style and the intrinsic two-line rule above. The sample-to-label gap is `5` logical points and adjacent vertical entries have a `4`-point gap. Filled swatches are `10x10`; stroke samples are `24x10`; point-shape, point-size, and angle samples use a `24x24` canvas. The mapped point-size radius remains `3..18`, the shape radius is `5`, and the angle needle radius is `9`. A text-size sample remains intrinsic to its mapped `10..32` font size rather than being padded to a fixed sample box. Android uses density-independent points and its `labelSmall` style; iOS uses points and its `caption2` style. Native glyph metrics may differ, but clients may not add a different entry box, row spacing, or sample padding that changes guide fit.
- `chart.legend.shape-label-adapter`: Any explicitly admitted automatic shape-label adapter uses one common inside-first pipeline. This includes a chart-family rule that lowers an automatic categorical guide and may include a future declaration that explicitly associates authored text with one painted shape. Semantic eligibility and stable owner/label identity resolve before screen layout. Localization, intrinsic two-line wrapping, and native glyph measurement then occur exactly once at the explicit text boundary; the pure placement tier must not call ICU, relocalize, reshape, shrink, ellipsize, rotate, or otherwise change that complete label block. Each admitted shape family supplies only its final painted geometry, preferred in-shape anchor, padded containment test, boundary anchor, and permitted outside-label adapter.
- `chart.legend.shape-label-placement`: Placement is resolved independently in the adapter's specified stable order; a guide-lowering adapter uses resolved category order, including its declared reverse. The complete measured label plus the family rule's padding first attempts the preferred in-shape anchor. A fit comparison permits at most one-half physical pixel of raster error. A fitting label remains inside its own final painted shape; one candidate's failure must never externalize a fitting sibling. Only failed candidates enter the outside collision plan and reserve outside space. That plan starts from the painted shape boundary and moves each label only as much as the family placement and collision rules require. Residual container or screen width is outer slack: it must not stretch the plot-to-label gap, lengthen a leader, resize the already solved mark, or cause a second classification pass. Any native-pixel placement is canonicalized at most once before obstacle and route preflight; paint consumes those identical stored positions rather than independently rounding the plot, labels, or leaders after spending the half-pixel tolerance.
- `chart.legend.shape-label-paint`: Every automatic label is painted exactly once, whether inside or linked outside, before any preview shell applies its documented outer clip. Those label views are decorative and accessibility-hidden: the existing canonical mark accessibility row, which already includes appearance-channel values, remains the label's single semantic owner. Source presentation changes only after a complete mixed placement succeeds. A guide-lowering adapter then suppresses its ordinary guide and restores that complete guide on failure; a future owner-associated authored-text adapter must define its own literal fallback. If ownership, complete text, containment, collision, clipping, or safe-area requirements cannot produce a plan, opportunistic automatic labels never reject an otherwise renderable chart. An adapter must be explicitly specified. Clients must not infer label ownership from layer order, proximity, matching display text or keys, or a shape that merely looks geographic. Lowering changes guide geometry, not semantic resource ownership. A suppressed guide reports the same entry, title, sample, label, and total primitive usage as its ordinary presentation, so automatic placement cannot bypass a guide or chart resource limit.
- `chart.legend.polar-radius`: **HARD INVARIANT:** every fullscreen polar family has one orientation-owned maximum mark radius before mark, label, or legend layout. In portrait, let `W` be the cutout-safe physical-axis width expressed in logical coordinates and let `s` be the display scale. That width owns the natural outer coordinate radius after the universal eight-logical-point paint inset on each physical side: `S0=(W-16)/2`, equivalently `S0Pixels=(WPixels-16*s)/2`. This inset is shared by Radar, pie, donut, ARC, and every other portrait polar mark; it is neither a label reservation nor a Cartesian height cap. A Cartesian nominal-height cap or floor never reduces `S0`. The portrait logical composition is `W x Hp`, where `Hp=max(W,resolvedPolarMinimumHeight,completePolarFootprintHeight)`. Thus real vertical host space and a complete automatic-label or title footprint may move or enlarge the composition around the width-owned mark, but may not substitute an inscribed label square or unrelated height for the natural-radius owner. An eligible automatic-label adapter may still select a slightly smaller largest feasible painted radius through its documented exact bounded planner. In landscape, the physical height `H` below the one top bar and above the stable bottom navigation/safe-area inset owns the natural radius `S0=H/2` and the base mark square is `H x H`. An outer host that already excludes an inset must not subtract it again. Radar, pie, donut, annulus, rose/radial ARC, and polar line/point/rule/text all use this same coordinate-family guard. Preview retains its established intrinsic polar height and documented outer-shell clip; it does not enter a fullscreen sizing branch merely because the application is portrait or landscape. Planner, paint/clip, grid, hit testing, tooltip and accessibility geometry consume the same radius, composition and origin.
- `chart.legend.polar-end-distribution`: An opportunistic fullscreen-landscape polar end legend replaces that fixed vertical gap with a measured distribution. For each categorical block, measure the title and every complete entry intrinsically. The title remains attached to the first entry by the `4`-point minimum gap, and adjacent entries retain that same minimum. Let `N` be the entry count and `B` be the resulting intrinsic block height. When `B <= H`, divide the surplus `H-B` equally among exactly `N+1` flexible spaces: one before the block, one after it, and one between each pair of entries. Round each absolute placement boundary to the nearest physical pixel; do not round and accumulate independent gap sizes. All categorical blocks therefore occupy the same measured charting height while preserving their own entry counts. A continuous block sharing that rail is vertically centered. When `B > H`, rows retain their intrinsic geometry and the automatic end placement fails the height-fit test below; clients never compress, clip, or overlap rows to manufacture a fit. This distribution applies only to the opportunistic end rail. Preview, portrait/bottom flow, and explicitly authored side guides retain their ordinary intrinsic geometry.
- `chart.legend.lane-order`: Facet headers and outside guide value lanes reserve space before the plot is mapped. Cartesian axis titles use only already-reserved guide space or the vertical safe rails of §6.4.3; they never enlarge a left/right lane. On each physical side, the first guide in the owner-key order of §6.4.2 is nearest the plot and later guides stack outward. Grid geometry is clipped to the plot and paints first; axis domains and ticks paint next; data marks paint in schema order; guide labels and titles paint last outside the mark clip. In fullscreen, an automatic appearance legend owned by a polar plot uses an orientation-responsive preferred side: `bottom` in the physical portrait viewport and physical screen `right` in the physical landscape viewport. The assigned polar square's equal dimensions are never an orientation signal. A resolved bottom appearance legend also participates in this fullscreen adaptation; explicitly authored `top`, `left`, and `right` remain exact. The landscape end choice is stable: a responsive retry never rewrites it or converts it into the temporary side overlay. The measured fit exception below is the only automatic end-to-bottom fallback. The plot uses the complete height below the single top bar. Before any chart-family layout, `W` and `H` exclude platform safe-drawing and physical display-cutout insets.
- `chart.legend.landscape-polar-guard`: This adaptation is guarded by coordinate family as well as presentation and orientation: it is active only for an interactive, fullscreen-filling, landscape polar pane with a positive finite charting height. A client must use one effective-position classification, including resolved-bottom-to-end pinning, for both end-legend preflight and legend rendering. It may not infer end occupancy independently in those two phases. Radar, donut, radial bar, polar point/line/rule/text, and future polar marks all use this same coordinate-family guard; Cartesian, concatenated, faceted, preview, portrait, and transition passes do not enter it.
- `chart.legend.landscape-polar-size`: For an unfaceted landscape polar plot without an automatic outside-label footprint, the known charting height `H` owns polar size before the end legend is measured. The client selects one guide cadence, measures its actual painted labels, and uses only the required vertical label/title reservation `V` to solve the circle as `R=H/2-V`. Horizontal label widths never become matching top and bottom insets and therefore never shrink this height-derived circle. Because the centered polar renderer applies `V` on both axes, the complete chart canvas is at least `H` wide. If either side's actual painted text extends beyond that base, the canvas grows symmetrically by the larger excess so width cannot silently recompute a smaller `R` through `min(width,height)`.
- `chart.legend.landscape-polar-placement`: The client then places this fixed chart footprint immediately to the physical left of the measured right legend with a `16`-point middle gap and centers their combined group in `W`, balancing the unused outer space. The right legend is therefore moved inward toward the chart rather than consuming the whole residual width. Legend measurement may translate that already-solved group, but must never determine, widen, narrow, or validate the polar circle.
- `chart.legend.end-fit-fallback`: Physical-end placement is opportunistic. It is used only when the complete `chart footprint + 16-point gap + intrinsic right legend` fits `W` and the intrinsic, pre-distribution right-legend height fits `H`, using the universal half-physical-pixel raster tolerance. A fitting categorical guide is then distributed through `H` by the rule above. If either comparison fails, the automatic appearance legend moves to `bottom` and uses the ordinary horizontal greedy flow. The fixed height-derived chart footprint is not reduced, the guide is not truncated, and the fit failure is not reported unless the resulting bottom layout also cannot represent essential content. A viewport, font, locale, or projection change retries end placement from a fresh measurement. Explicitly authored `right` guides do not use this automatic fallback.
- `chart.legend.diameter-preference`: Once the plot already consumes that maximum physical height, the 200-point interactive diameter is a preference rather than a reason by itself to move the legend; the two-dimensional end-guide fit above may still select bottom. Positive geometry and a valid polar-guide plan remain mandatory. Preview retains the ordinary automatic `bottom` default so the fixed summary does not depend on application-window orientation.
- `chart.legend.arc-label-priority`: An eligible ARC partition's automatic outside-label subset from §11.4.1 is nearer the plot than every automatic appearance legend. It is reserved and measured first; fitting in-shape labels consume no outside space. The ARC adapter's labels, leaders, and complete polar frame form one tight chart footprint. That footprint is the union of final painted bounds, not a proposal or flexible container, so an `outer_radius < 1` cannot turn unused polar-frame space into leader length. Center the tight footprint in the available safe width without changing its internal geometry. It then participates in the two-dimensional opportunistic-end test above. A lower-priority automatic right legend is placed outside the footprint with the ordinary `16`-point middle gap when the composition fits; otherwise that legend moves to bottom. It may not hide, overlay, reorder, truncate, or replace inside or linked labels to make itself fit. Explicit non-bottom legends retain their authored side and ordinary owner-key order. Polar grids follow the existing spoke/circle rules, including suppression of a duplicate theta-one spoke and a zero-radius circle. A renderer may thin, wrap, or ellipsize only by the rules above. If the resolved lanes cannot leave the minimum plot width required by §6.4.8 or an essential label cannot be represented, the complete chart follows the failure policy in §10 rather than overlapping or relocating an explicit guide.
- `chart.legend.guide-accessibility`: Accessibility traverses chart title, guides by physical side and inward-to-outward order, then marks. It exposes every unabridged guide title and raw candidate through the typed localized rendering above, including candidates thinned visually; a span entry also exposes its target interval. Legends expose the appearance role and sample meaning, and facet headers label the pane they govern.

### 6.4.8 Deterministic responsive guide overlay

Owner: [2d.md#648-deterministic-responsive-guide-overlay](../reference/charts/2d.md#648-deterministic-responsive-guide-overlay)

- `chart.overlay.usable-viewport`: The containing chart shell first establishes one usable visible viewport after safe areas, system bars, and persistent application chrome. Chart controls use the upper title safe rail and do not reduce or overlay the charting region. Android measures in density- independent points and iOS in points.
- `chart.preview.floor-and-viewport`: The `180`-point floor wins over the cap: a short pane, such as a phone in landscape, still gets the V1 region and the excess is reached by the enclosing scroll. The preview viewport keeps the V1 proportion, assigning three quarters of its height to that region: its height is exactly `4R / 3`, so the floor reproduces the V1 `240`-point viewport. `R` is re-evaluated when the pane's width, height, or width class changes; that re-layout is a new presentation of the same snapshot and never changes the data. A non-finite or zero `Hp` leaves only the floor and the width term. Neither this rule nor `chart` changes fullscreen sizing, and a `Chart3D` preview keeps its own fixed `240`-point viewport.
- `chart.preview.fullscreen-height`: In fullscreen, let `W,H` be the current usable viewport width and height. The assigned charting-region height `R` is:

  ```text
  landscape or square (H <= W): R = 0.75 * H
  portrait (H > W):            R = min(0.75 * H, W)
  ```

- `chart.preview.concat-and-facet-panes`: The portrait width is the deterministic stand-in for the height visible after rotation to landscape. This supersedes a fixed 300-point portrait cap while still preventing a tall phone from stretching one plot arbitrarily. An unfaceted unit/layer and each concat child use exactly `R`. For an `hconcat`, let `Wv` be the current usable visible viewport width after shell insets. Every child receives cross-axis pane width `P = max(Wv, 280 points)`; the client must never divide `Wv` by the number of children. Preview keeps the root title and Chart Explorer control in a shell rail of width `Wv`, outside composition clipping, then clips later panes. Fullscreen uses the same per-pane rule and one horizontal composition scroller, so every complete pane remains reachable in portrait, landscape, and after rotation. A facet chart assigns `R` to its complete pane grid, subtracts authored row gaps, and divides the remainder equally among facet rows. An established per-pane geometry minimum may increase the complete grid beyond `R`; panes never collapse to satisfy the reservation. A `vconcat` likewise gives each child its own `R`, so later children remain reachable through the same outer vertical viewport.
- `chart.preview.outer-viewport`: Chart title and appearance legends are outside that assigned charting region. They never shrink or stretch it. In particular, a left/right legend taller than `R` increases the complete scroll content height while the adjacent plot remains `R`; top/bottom legends precede or follow it normally. Preview and fullscreen have different shell semantics. A preview is a bounded, gesture-neutral summary: excess legend or composition content is clipped by its `4R / 3` preview viewport and is reached only by opening Chart Explorer. It has no internal vertical or horizontal scroller. Fullscreen Chart Explorer owns exactly one vertical chart viewport over the complete title, charting regions, and legends; its excess content remains reachable through that outer scroll. A renderer must not add a nested legend scroller, cap legend height, discard entries, or fall back merely because complete vertical content is taller than either viewport.
- `chart.preview.initial-scroll`: Initial presentation is data-focused. Preview places its fixed control rail and first charting region at the leading edge and does not establish mutable scroll state. In fullscreen, starting from semantic scroll position zero, the client makes the minimum nonnegative vertical displacement that fully exposes the first charting region. This leaves up to the permitted first quarter available for preceding legend/title content. If that charting region itself is taller than the viewport, its top aligns with the viewport top. Subsequent measurement, rotation, Dynamic Type changes, and recomposition never reset a fullscreen user's scroll position; a new immutable chart snapshot establishes a new initial presentation.
- `chart.overlay.minimum-plot-width`: The preferred minimum usable plot width is `200` logical points: Android uses density-independent points and iOS uses points. For each plot or facet pane, let `C` be the horizontal width assigned to its plot-and-guide frame after shell padding and concat/facet allocation but before any left/right guide lane reserves space. Its required usable plot width is `M = min(200, C)`. `M` is a post-guide plot measurement, not the outer chart width. A container narrower than `200` points is therefore valid and the chart must not fall back merely because its plot cannot reach `200`; it must still reclaim guide-reserved width until it reaches `C` or exhausts the policy below. Integer-pixel rasterization may satisfy `M` at the nearest physical pixel, with at most one-half physical pixel of error. The renderer measures final localized text with the user's active accessibility font metrics before choosing presentation. Native font metrics may therefore change whether `M` is crossed, but may not change the policy below.
- `chart.overlay.polar-preview-diameter`: An interactive polar pane applies that same preference to its equal-aspect diameter. A bounded, gesture-neutral preview instead requires a strictly positive post-guide diameter and validates every final polar label against measured bounds and collisions. It does not reuse the interactive 200-point diameter: at the `180`-point preview floor, any outside polar label would make that condition structurally impossible before candidate thinning could run, and a taller responsive `R` does not reinstate it. This preview rule never removes a semantic guide candidate; thinned candidates remain in accessibility, and opening Chart Explorer restores the interactive diameter preference.
- `chart.overlay.facet-columns`: Facet columns are responsive allocations, never intrinsic `200`-point widgets. After subtracting the one resolved row-header lane from the facet grid's pre-guide width, the renderer divides every remaining point equally among the nonempty facet columns in source-resolved order. A one-column facet therefore consumes all remaining width and leaves no trailing blank column. That equal share is the pane's `C`; a share below `200` is valid under `M = min(200, C)` and cannot itself cause fallback. Column headers are centered within the same equal shares. Their existing complete-label and two-line categorical wrapping rules still apply; the renderer does not restore an intrinsic pane minimum to make a header fit.
- `chart.overlay.two-stage-policy`: Every guide first participates in the ordinary reserving layout in §6.4.7. If that complete plan leaves a plot narrower than `M`, every Cartesian axis, offset tier, value rail, or span rail resolved to the physical left or right side changes to temporary overlay presentation together, and the client remeasures. If that stage-one plot is still narrower than `M`, every visible appearance legend on the left or right side joins the same overlay, and the client remeasures a second and final time. A legend never moves when the axis-only stage already restores the minimum. Top and bottom guides remain outside because converting them cannot reclaim horizontal width. Facet headers and polar guides never move or disappear. This is a mandatory runtime policy, not author metadata: no `ChartGuide` property enables, disables, or reorders it. If the final Cartesian plot or interactive polar diameter is still narrower than `M`, or any final plot has nonpositive height, the complete chart follows the failure policy in §10. Apart from the closed gesture-neutral polar-preview rule above, there is no device-size exception beyond `M = min(200, C)`, partial conversion within either stage, orientation-specific guess, or additional platform heuristic.
- `chart.overlay.control-set`: Exactly one chart-control set belongs to one rendered chart root. A facet pane, layer unit, or concat child never creates another set; its viewport actions are coordinated through that root set. In fullscreen, one guide control is present whenever the runtime policy places at least one resolved guide in overlay mode. Preview exposes only its Chart Explorer presentation action and never guide or zoom controls. Every control is a visible `44` by `44` point minimum touch target. The upper safe rail packs complete localized guide titles into its leading remainder and the one control set at its physical trailing edge. When both fit they share a row. When they do not, the title or controls wrap to additional complete safe-rail rows in reading order; that vertical growth belongs to the outer chart scroll and can never reduce, cover, or reject the plot. Controls appear in this fixed physical left-to-right order when present: Guides, Zoom out, Reset, Zoom in, then Fullscreen or Close. Hidden/inapplicable controls leave no empty slot. The guide control's closed and open accessibility names use the localized client resources `ChartShowGuides` and `ChartHideGuides`; no schema token or native name is shown. Activating it opens or closes every overlay guide atomically. Guide controls are operable only in fullscreen Chart Explorer. Pointer, keyboard, switch-control, and screen-reader activation are equivalent.
- `chart.overlay.open-geometry`: While open, an overlay guide uses the same resolved candidates, component switches, side, owner order, localization, and accessibility entries as its outside form. Its domain remains on the selected plot edge, but ticks point inward and its label and title lanes mirror inward over the plot instead of reserving exterior space. On one side the first axis, offset, value, or span owner is nearest that edge and later such owners stack farther inward. Side appearance legends retain their ordinary vertical block geometry and authored side, and stack inward after those mirrored lanes in their resolved owner order. Their complete title, samples, labels, and accessibility entries remain unchanged; legend labels do not gain the axis-label ellipsis permission below. Overlay components paint after marks on a theme surface that preserves text contrast and may temporarily obscure data; they never remap, resize, pan, select, or hit-test marks. Existing deterministic collision rules still apply inside the fixed plot bounds. Ellipsis is permitted only for candidate labels in this temporary overlay presentation; outside-guide labels, legend labels, and every guide title remain unabridged. For a horizontal overlay, a candidate's label cell extends horizontally from the midpoint to the preceding retained anchor to the midpoint to the following retained anchor, clipped to the plot; the first and last cells use the corresponding plot edge. Its height is the ordinary one- or two-line label lane. A vertical overlay uses the analogous y interval for cell height and the mirrored outside-guide lane for cell width. Mirrored lanes retain their measured outside widths and owner stacking, then intersect the plot; no equal-width or fixed-width lane is invented. Each cell reserves the ordinary four-point inter-label gap, split equally across its two anchor-axis sides.
- `chart.overlay.label-ellipsis`: A categorical overlay label first wraps to at most two lines. Using extended grapheme clusters, the renderer puts the longest prefix that fits on the first line, preferring the last Unicode line-break opportunity within that prefix; leading and trailing break whitespace is not painted. The remaining text forms the second line. A noncategorical label remains one line. If a resulting line does not fit the cell width, the renderer removes trailing extended grapheme clusters until appending the single Unicode ellipsis character `…` fits. It never emits three dots, splits a grapheme cluster, changes the semantic candidate, or uses the truncated text for collision, grouping, or identity. The complete unabridged localized value remains the accessibility label. If the required one- or two-line font height does not fit the cell height, the ellipsis alone does not fit the cell width, a measured mirrored lane has no nonempty intersection with the plot, or an unabridged title cannot fit its overlay lane, the complete chart follows the failure policy in §10.
- `chart.overlay.side-legend`: A side appearance legend keeps its intrinsic vertical extent while overlaid. If it is taller than the assigned charting region, the legend extends the one outer vertical chart viewport exactly as it did outside the overlay; the plot height remains `R`, and there is still no legend-only scroller or height cap. The left and right overlay stacks, including their preceding mirrored lanes, must fit without crossing. Otherwise the complete chart follows the failure policy in §10. Closing the overlay removes their visible paint but not their guide resource counts; it also removes their direct accessibility entries until the localized guide control opens the overlay again.
- `chart.overlay.dismissal`: The overlay remains open until the user activates the guide control again, uses the platform accessibility dismiss action, or taps outside the chart. It has no timeout. A layout change reruns the complete policy and closes the overlay; if the guides now fit outside they return outside, otherwise the closed overlay affordance remains. Overlay mode does not reduce guide candidate or primitive resource counts and cannot rescue a semantically over-limit chart.

### 8.1 Localization and schema-document dependency

Owner: [2d.md#81-localization-and-schema-document-dependency](../reference/charts/2d.md#81-localization-and-schema-document-dependency)

- `chart.l10n.no-document-dependency`: Chart semantics have **no schema-document dependency**. A client must be able to parse and validate geometry, domains, grouping, and interaction identity from only the language-neutral schema and payload:

  ```text
  schema + ScriptValue snapshot -> semantic ChartSpec
  semantic ChartSpec + resolved document? + client resources -> presentation
  ```

- `chart.l10n.title-optional`:

  | Existing localized source | Chart use | Required? |
  | --- | --- | --- |
  | Type `DisplayName` | Chart or composition title | No; omit an empty title. |

- `chart.l10n.description-optional`:

  | Existing localized source | Chart use | Required? |
  | --- | --- | --- |
  | Type `Description` | Long help and accessibility context | No; omit when empty. |

- `chart.l10n.field-name-optional`:

  | Existing localized source | Chart use | Required? |
  | --- | --- | --- |
  | Field/member localized `Name` | Guide, legend, and tooltip caption | No for geometry; omit an optional caption when empty. |

- `chart.l10n.field-description-optional`:

  | Existing localized source | Chart use | Required? |
  | --- | --- | --- |
  | Field/member `Description` | Tooltip help and accessibility detail | No; omit when empty. |

- `chart.l10n.object-resolver-required`:

  | Existing localized source | Chart use | Required? |
  | --- | --- | --- |
  | Existing Libertas object resolver | Dynamic display name for a `Device`, `VirtualDevice`, `User`, `Endpoint`, or `LanDevice` reference | Required only when that reference is visibly encoded. |

- `chart.l10n.client-catalog`:

  | Existing localized source | Chart use | Required? |
  | --- | --- | --- |
  | Client localization catalog | Generic actions, fallback/errors, mark names, and accessibility grammar | Required of the client, not of the App schema. |

- `chart.l10n.resolution-and-no-synthesis`: Resolution uses the platform's existing locale priority and default-locale fallback. Enumeration values use their localized member documents. Every Libertas object type uses its existing dynamic, permission-aware client resolver and ordinary unavailable-reference behavior; chart code must not invent a second resolver or require an App-supplied label. Names may change at runtime without changing the raw reference identity. If an optional localized value remains empty, the client omits it. It must not synthesize a caption from `NativeName`, a scale ID, channel token, `Chart` value, source-code identifier, or hard-coded English. If an essential visible category cannot be localized or resolved after normal fallback, the client follows the chart failure policy in §10 instead of presenting an inaccessible or misleading chart.
- `chart.l10n.generic-client-wording`: generic client wording comes from the client's Libertas string resources;
- `chart.l10n.diagnostics-and-a11y-sentences`: Diagnostics may include stable native names in developer logs, but those names are never user-interface fallback text. Accessibility sentences are assembled from localized field/category values with localized client templates; they are not constructed by concatenating English fragments in renderer code. Chart metadata never embeds a format string.

### 9. Interaction model

Owner: [2d.md#9-interaction-model](../reference/charts/2d.md#9-interaction-model)

- `chart.interaction.hit-and-slice`: a tap or pointer focus must first hit at least one datum's final painted geometry within the platform's normal touch/pointer radius; plot background dismisses. In a Cartesian plot, a directly hit polygon competes with other directly hit geometry by full geometry distance and then reverse paint order, but never joins an X slice. If the winning direct hit is not a polygon, the client resolves exactly one horizontal slice among the remaining Cartesian marks by rendered X distance, then full geometry distance, then reverse paint order. Continuous X distance is measured after the resolved forward transform and current normalized viewport, so zoom increases raw X resolution without changing source data;
- `chart.interaction.slice-identity`: a slice has one X-scale owner and exact source identity. A nonempty compatible scale ID is a shared owner; an unnamed X scale is local to its unit. Point slices use the effective scalar type and exact raw X identity. X/X2 marks use the unordered endpoint pair and never merge with a point slice. Categorical identity is the exact raw key. Pixel epsilon, localized labels, and coincident normalized positions never merge different slices or independent scales;
- `chart.interaction.grouped-callout`: after the slice is chosen, the grouped callout contains only distinct source rows in that slice whose final painted vertical geometry is within the platform radius. The winning row appears first; peers are ordered by vertical distance and stable composition/row order. Selection is confined to the current concat child and facet pane, while distinct detail series and layers at that X remain eligible;
- `chart.interaction.no-interpolated-row`: a line or rule may use interpolation only to measure its painted hit geometry. It resolves to the nearer originating source row in output X, choosing the later row at an exact midpoint; tooltip values, typed requests, and selection identity never synthesize an interpolated row. Cartesian intervals use their final clipped/minimum-width footprint for hit testing;
- `chart.interaction.polar-selection`: polar marks retain geometry selection because they have no Cartesian X slice. Point, text, and arc resolve to their source row. A polar line or rule resolves a hit segment to its nearer originating endpoint row in screen distance, choosing the later canonical row at an exact midpoint. Competing polar hits use full geometry distance and then reverse paint order;
- `chart.interaction.polygon-hit`: a polygon hit uses the exact final clipped path: containment uses the `evenOdd` fill, so a point in a hole is not a fill hit, and stroke proximity applies only when a visible stroke was painted. A boundary hit resolves like a line edge. A fill-interior hit resolves to the nearest finite, validly mapped source vertex in screen distance across every subpath, choosing the later canonical vertex at an exact tie. Overlapping polygons use reverse paint order to establish the winning hit. For a polar polygon, that winning vertex establishes one mapped theta spoke; the callout then includes one finite, valid original source vertex from every polygon detail group in the same pane on that spoke. Theta zero and one are the same spoke. Groups retain stable paint order, and each selected vertex's original `ScriptValue`, not a synthesized group or intersection, owns its tooltip and typed request;
- `chart.interaction.cartesian-callout`: the Cartesian callout presents at most one common horizontal header, followed by the selected rows' remaining tooltip fields. It promotes and removes only X/X2 source fields present in the authored or default tooltip projection; an explicit omission remains omitted and a distinct tooltip-only field remains. When both endpoints are present, one range header preserves the interval. The callout is anchored at the interaction point, prefers the space above, flips below near the top edge, remains inside the complete physical chart, and stays bounded and internally scrollable. A facet pane confines selection identity but is not the callout's horizontal viewport; sibling facet space remains available to the callout. The desired outer width is the widest complete logical row plus both content insets and exactly one dedicated, stationary close-control slot. The complete physical chart width after edge insets is the only horizontal cap: no smaller fixed or style maximum may force overflow while unused chart width could expose that row. The close control never overlays or scrolls with content. Each localized caption and typed value is an unabridged, non-wrapping token. Only when the complete row plus its reserved chrome exceeds that physical bound does the content scroll horizontally rather than wrap or truncate. It follows layout changes through a normalized interaction anchor and provides both a visible close control and the platform accessibility dismiss action. The ordered selected-source identity owns both scroll axes: replacing one nonempty selection with a different identity resets the callout to top-leading before paint, while anchor movement or a repeated tap on the same ordered identity may retain its scroll offsets. A newly selected datum can therefore never inherit a prior tooltip's scrolled state and mask its first row or leading token;
- `chart.interaction.polar-callout`: a polar or polygon callout presents each selected source row's authored or default tooltip projection without synthesizing a Cartesian horizontal header. It uses the same bounded placement, scrolling, close, and accessibility behavior;
- `chart.a11y.focus-values-and-units`: accessibility focus exposes the mark, localized series/category, and the original typed channel values rendered through the localization and permission-aware name-resolution rules in section 8.1, plus display units from effective `PhysicalUnit` metadata using the long localized form. Unit prose present only in field documents or descriptions is not semantic unit metadata and is not parsed or synthesized into a value. Polygon separators and invalid vertices are not focusable; polygon vertices traverse in group paint order, subpath order, and canonical vertex order. Polar accessibility exposes the raw theta and radius values and never substitutes normalized turns or screen radii;
- `chart.a11y.virtualized-sequence`: native accessibility may virtualize this mark sequence instead of allocating one native accessibility object per admitted source row. A virtualized sequence initially exposes its first eligible mark, resets to that mark only when the complete chart snapshot is replaced, and provides localized previous and next mark actions in the same canonical traversal order. Each action exposes the complete localized semantics of the newly current mark. It may not skip a row because the row is offscreen or visually resampled. This is an accessibility lowering mechanism, not chart grammar, visible chart chrome, selection, or a typed App action;
- `chart.interaction.preview-and-zoom-range`: preview charts do not select marks, invoke requests, pan, zoom, or otherwise mutate a viewport. They retain the deterministic initial discrete-position window and overflow indicator specified below; Chart Explorer makes every authored category reachable. An interactive chart with a continuous `x` scale owns a transient output-space viewport over the resolved snapshot domain. Its minimum zoom is `1x` (the complete domain) and its maximum zoom is `8192x`; zooming and panning clamp to that domain and never rewrite source rows, scale bounds, tooltip values, or task-computed interval endpoints;
- `chart.interaction.polar-no-viewport`: version-one polar panes do not pan or zoom. Their complete resolved theta and radius domains remain visible in preview and fullscreen. A Cartesian polygon continues to clip and hit-test through the ordinary continuous-X viewport;
- `chart.interaction.initial-continuous-viewport`: for a continuous-only X dimension, fullscreen entry, Reset, and a replacement snapshot use the complete `1x` semantic X domain. The discrete-position rule below is the only initial-viewport exception: it starts at its category-density minimum rather than compressing authored category lanes;
- `chart.interaction.gestures`: pinch zooms around the gesture focus where the platform exposes it. A horizontal drag pans once zoomed, while vertical drag remains owned by the surrounding fullscreen scroller. Tap remains selection. Visible localized zoom-in, zoom-out, and reset controls provide pointer and accessibility equivalents; Reset and full zoom-out restore the continuous-only `1x` view or the discrete-position category-density minimum defined below. Directional ownership is decided before a child chart pan recognizes: ignoring a vertical drag only after recognition is not sufficient because it can still prevent the ancestor scroller from moving. High-frequency pan and pinch updates may be coalesced to the display cadence, but gesture completion commits the exact final viewport rather than dropping the last delta. To avoid invalidating every linked native chart on every pointer sample, the actively manipulated pane may use a gesture-local viewport; panes sharing the same scale ID converge atomically when the gesture commits and must not retain mismatched viewports afterward;
- `chart.interaction.normalized-viewport`: viewport coordinates are normalized scale-output fractions, so linear, `log`, `symlog`, `sqrt`, temporal, and `reverse=true` scales all use the same already-resolved forward/inverse transform. Automatic guide ticks and labels use the stable cadence plan and bounded visible guide run in §6.4.5; viewport movement never reruns cadence selection. A replacement snapshot creates a new initial viewport;
- `chart.render.category-pitch`: the minimum leaf-category pitch is exactly `20` logical points on both platforms. For one unit and one dimension, the leaf-lane count is the primary category count multiplied by the matching `xOffset` or `yOffset` count when that band has an offset, otherwise by one. A point scale has no child band. The physical plot's leaf-lane count for that dimension is the maximum required by any contributing unit. This `20`-point rule is derived from the established `14`-point interactive lane and the 72-percent band cap (`14 / 0.72`, rounded upward); it is not a platform theme default;
- `chart.render.category-extent`: let `P` be the post-guide physical plot extent and `L` the leaf-lane count in one dimension. Its virtual category-content extent is `E=max(P,20*L)`. Every Cartesian scale in that physical plot maps its complete normalized output through the same `E`-point content extent, so layered independent scales remain spatially coherent. The visible window is exactly `P` points and pans continuously within `0...(E-P)` without snapping. When `E=P`, that dimension has no category viewport. Concat children resolve independently; facets and flattened layers that share one physical scale share its viewport;
- `chart.render.category-initial-window`: a new snapshot initially exposes the visual top-left of category content: the leftmost X output and topmost Y output after `reverse` has been applied. Thus `reverse=true` reverses the visible category order rather than being silently cancelled by initialization. A later size, font, overlay-guide, or rotation change recomputes `E` and `P`, preserves the normalized leading content position, and clamps it; it never resets a user's category position;
- `chart.render.category-preview-and-fullscreen`: preview retains the same virtual category extent and deterministic visual top-left window but accepts no pan, zoom, selection-scrub, or accessibility viewport-adjustment action. Its noninteractive position indicator truthfully discloses clipped category content, and the containing endpoint workspace exposes the localized action that opens Chart Explorer. In fullscreen, an overflowing X dimension accepts horizontal movement and an overflowing Y dimension accepts vertical movement. When both overflow, including a two-dimensional heatmap, one drag applies both pointer components simultaneously. When only one overflows, only that component is consumed. At a Y boundary, the exact unconsumed vertical delta chains to the one outer Chart Explorer scroller; horizontal movement never blocks that chaining;
- `chart.render.category-density-zoom`: one physical dimension owns one normalized viewport even when independent categorical and continuous scales are layered on it. Its category-density minimum zoom is `Zc=max(1,E/P)`. A continuous X interaction may zoom further but never below `Zc`; its maximum is `max(8192,Zc)`. Reset and a replacement snapshot restore the visual top-left category window at `Zc`, not a category-compressing full-domain view. A category-only plot adds scrolling and indicators but no zoom buttons. A mixed continuous/category X plot keeps the ordinary zoom controls and uses this same viewport for every X scale. Y category overflow likewise applies one viewport to every Y scale because V1 has no independent continuous-Y zoom;
- `chart.render.scroll-indicator`: each overflowing category dimension paints a noninteractive scroll-position indicator inside the plot: a `2`-point track inset `3` points from the bottom for X or right edge for Y, and a proportional thumb with a minimum length of `16` points. The thumb position is `(offset/(E-P))` over the remaining track. The indicator uses the theme's secondary foreground at 40-percent opacity, paints above marks, is excluded from mark hit testing, and is not the resampling indicator because no data was discarded. The plot accessibility node always exposes the localized visible category range. In fullscreen it additionally exposes forward/back scroll actions that move by one visible window minus one leaf lane, with a minimum movement of one leaf lane. Preview exposes no viewport-adjustment action; its containing endpoint workspace exposes the localized action that opens Chart Explorer;
- `chart.render.culling`: clients cull offscreen discrete marks and guide entries before allocating native scene objects. Path lowering must retain any boundary-crossing segment needed for the exact clipped result, and culling never changes domains, canonical order, grouping, tooltip/source-row identity, accessibility, or resource validation. Category labels may use the existing deterministic guide thinning and responsive overlay policies, but neither policy removes its category geometry;
- `chart.render.resampling-scope`: automatic visual resampling applies only to Cartesian `line` and vertical `area` runs whose X coordinates are monotone after facet/detail partitioning, canonical ordering, invalid-row breaks, and visible-viewport culling. Equal X positions are monotone. A non-monotone line, a horizontal `area(x,x2)`, every polar path, and every other mark remain exact; category scrolling and offscreen culling are not resampling;
- `chart.render.resampling-algorithm`: let `W=max(1,ceil(P))`, where `P` is the final plot width in logical points. Visible culling first retains every segment needed to cross the viewport boundary. If one resulting eligible run contains at most `4*W` vertices, the client paints it unchanged. Otherwise map each vertex X through the resolved scale and viewport, clamp its logical-point bucket to `0...(W-1)`, and retain from every nonempty bucket the canonical first row, lower-envelope row, upper-envelope row, and canonical last row, deduplicated and returned in canonical source order. For `line`, both envelopes are Y. For a vertical area, the lower envelope is `min(y,y2)` and the upper envelope is `max(y,y2)`; an omitted `y2` uses the resolved zero baseline. Extrema ties choose the earlier canonical row. This yields no more than four painted vertices per logical plot point while preserving endpoints and visible extrema. Resize, zoom, pan, guide-overlay, and facet-layout changes recompute the result from the immutable full run; a client never resamples an already resampled path;
- `chart.render.resampling-not-transform`: resampling is a renderer-owned level of detail, not a data transform. Full source rows still determine domains, guides, grouping, structural/guide resource accounting, tooltip values, requests, and the ordinary data view. Hit testing may search the full source path so a retained visual segment resolves to an original endpoint row; it never synthesizes a bucket row. Accessibility announces that automatic resampling is active and retains original typed values;
- `chart.render.resampling-indicator`: whenever at least one painted run actually loses vertices, the plot paints one noninteractive resampling indicator above marks at its top-left: a `16`-point square inset `4` points, using the chart surface at 85-percent opacity, a `3`-point corner radius, and a centered `≈` glyph in the secondary foreground. The glyph is not localized; its accessibility label is the localized equivalent of “Automatically resampled.” It is absent when the current viewport paints every eligible vertex and is separate from category scroll indicators;
- `chart.interaction.date-only-viewport`: a continuous DateOnly X viewport is the typed exception to arbitrary fractional edges. After each requested zoom or pan is converted to Gregorian day ordinals, both ordered edges round to the nearest integral ordinal with binary64 ties-to-even and clamp to the resolved domain. Equal snapped edges are a valid one-date degenerate viewport; further zoom in is a no-op. That date uses the degenerate-domain rule and maps to the plot midpoint, every other X value clips outside the viewport, and a zero-width clipped span intersection remains empty. The client derives the stored normalized fractions back from those snapped ordinals. Consequently every visible DateOnly endpoint, regenerated priority tick, and inverse-mapped clipped span bound remains an exact typed `yyyyMMdd` date rather than a platform-specific partial-day value;

### 10. Validation and fallback

Owner: [2d.md#10-validation-and-fallback](../reference/charts/2d.md#10-validation-and-fallback)

- `chart.fallback.clients-revalidate`: Android and iOS validate the effective schema and runtime values because a remote or newer schema is still untrusted input.
- `chart.fallback.validate-placement`: chart placement and complete `List<Table>` unit shape;
- `chart.fallback.validate-composition-children`: composition child types;
- `chart.fallback.validate-nesting`: layer/concat nesting and nested-layer flattening rules;
- `chart.fallback.validate-tokens`: known mark, composition, channel, scale, guide, and property tokens;
- `chart.fallback.validate-multiplicity`: channel multiplicity and mark-required channel combinations;
- `chart.fallback.validate-coordinates`: exclusive Cartesian/polar coordinate forms and polar-layer compatibility;
- `chart.fallback.validate-scalar-types`: scalar type/channel compatibility;
- `chart.fallback.validate-endpoints`: paired endpoint and scale compatibility;
- `chart.fallback.validate-finite-values`: finite numeric values and valid literal styles;
- `chart.fallback.validate-polygons`: polygon subpath structure and canonical raw group-style constancy;
- `chart.fallback.validate-shared-scales`: shared-scale compatibility;
- `chart.fallback.validate-guides`: guide placement, source/target matrices, shared ownership, localization, and the single-visible-guide rule for each polar position family;
- `chart.fallback.validate-resource-limits`: resource limits.
- `chart.fallback.atomic-replacement`: Replace the failed chart atomically; never silently omit an unsupported layer or display altered data meaning. A presentation failure must not reject an otherwise accepted protocol response, alter its data, crash the screen, or change pending-operation ownership. Diagnostics remain bounded. The [endpoint presentation contract](../reference/endpoint-presentation.md#endpoint-workspace) owns workspace and navigation continuity. Platforms may choose different failure presentations while preserving these rules.

### 11.4.1 Automatic in-shape and linked ARC labels

Owner: [2d.md#1141-automatic-in-shape-and-linked-arc-labels](../reference/charts/2d.md#1141-automatic-in-shape-and-linked-arc-labels)

- `chart.render.arc-labels.eligibility`: A client may lower one automatic categorical appearance guide owned by an eligible `arc` unit into automatic shape labels. This is an ARC-family rule, not a donut-only rule: a pie with `inner_radius=0`, an annulus with a positive inner radius, and a rose/radial categorical partition with row-varying radii use the same planner. The first qualifying guide in resolved guide order must meet every condition below:
- `chart.render.arc-labels.condition-theta-radius`: its owner has explicit `theta` and `theta2`; radial bindings are either both absent, resolving every row to pie radii `0...1`, or both `radius` and `radius2` are present;
- `chart.render.arc-labels.condition-position`: its position is absent or `auto` and its labels component is enabled;
- `chart.render.arc-labels.condition-target`: its target is categorical `fill`, or categorical `color` when the owner has no explicit `fill` binding; other targets and a color shadowed by explicit fill retain their ordinary guide;
- `chart.render.arc-labels.condition-sole-unit`: the owner is the pane's only unit with painted rows, and no other resolved guide occupies physical left or right;
- `chart.render.arc-labels.condition-row-geometry`: every painted row has finite `0<=inner_radius<outer_radius<=1`, positive angular span, fully opaque fill, and no visible nonzero-width arc stroke;
- `chart.render.arc-labels.condition-complete-turn`: the rows' angular open interiors do not materially overlap and their positive authored spans form one complete turn under the portable arrangement tolerance below, so they are a categorical pie/annulus/rose partition rather than occluding radial layers; shared boundaries are allowed, inner and outer radii may vary by row, but a material gap is not eligible for automatic rearrangement;
- `chart.render.arc-labels.condition-one-row-per-category`: every painted row has exactly one target-category identity, every resolved target category owns exactly one painted row, and no two rows own the same identity; and
- `chart.render.arc-labels.condition-no-polar-guide`: the plot has no visible `theta` or `radius` guide.
- `chart.render.arc-labels.ineligible-keeps-guide`: Failure of any condition keeps the complete ordinary appearance guide and is not a render rejection. In particular, a varying-radius rose is not rejected or forced into common-radius geometry: its row-specific painted outer boundary is the leader anchor and Apache ECharts' common maximum outer radius owns the elbow envelope. A future adapter is required for overlapping ARC layers, translucent compositing, or visible-stroke paint bounds.
- `chart.render.arc-labels.exactly-once`: For a successful plan, every category appears exactly once as either an in-shape label or a linked outside label. Text is the complete localized category label; clients do not synthesize a value or percentage from tooltip fields. Localization, the intrinsic two-line categorical wrapping rule of §6.4.5, native shaping, and native measurement happen once before geometric search. Automatic labels are never rewrapped, truncated, ellipsized, scaled, rotated, hidden, or semantically thinned.
- `chart.render.arc-labels.leaders`: In-shape labels are centered. Outside labels align toward their leader; labels above or below the plot remain upright and centered. Leaders attach to the owning row's painted boundary, including row-varying radii. They must not cross one another, another label's ink, or a filled sector. Complete labels, leaders, and the guide title fit the cutout-safe composition with the contract's half-physical-pixel raster allowance.
- `chart.render.arc-labels.title`: The guide title appears once, inside the common unpainted hole when it fits at the minimum radius, otherwise in the ordinary guide's title-only lane. A pie and annulus with the same outer geometry retain the same natural mark scale; a hole changes title placement, not scale. The minimum painted radius is 100 logical points. The title uses the 11-point guide-title style and four-point clearance. A bottom title lane has an eight-point gap; a physical-end lane has a sixteen-point gap. The title is never omitted to make labels fit.
- `chart.render.arc-labels.turn-tolerance`: A complete categorical partition is eligible only when its total span and adjacent seams differ from one complete turn by no more than `nextUp(1e-9)` binary64 turns. The accepted arrangement preserves category order, identity, and proportions. A material gap or overlap retains authored geometry and the ordinary guide.
- `chart.render.arc-labels.fullscreen-layout`: Full-screen portrait uses the complete safe width `W` and natural radius `(W-16)/2`; the composition height includes the full label/title footprint and is at least `W` and the polar minimum. Full-screen landscape uses the complete height `H` below the title bar and above the stable bottom inset, with an `H x H` plot centered in safe width `W`. Insets are applied once. If `W < H`, automatic labels fall back to the ordinary guide. Additional safe width does not lengthen leaders. The combined plot/title/label footprint is centered without changing its accepted internal arrangement. An auxiliary guide that does not fit is available through **Show Guides**.
- `chart.render.arc-labels.preview-and-fallback`: Preview retains its intrinsic polar host, sized from the preview's square `R`. It applies the `4R / 3` preview viewport clip only after the complete logical layout succeeds; it has no label scroller. Chart Explorer reveals the complete footprint. If complete automatic placement fails, restore the complete ordinary guide atomically. Do not reject an otherwise valid chart, clip label text, omit categories, or weaken collision requirements to retain automatic labels.

### 11.10 Server-projected maps, networks, and flows

Owner: [2d.md#1110-server-projected-maps-networks-and-flows](../reference/charts/2d.md#1110-server-projected-maps-networks-and-flows)

- `chart.render.map-labels-literal`: The automatic shape-label policy of §6.4.7 is universal, but an authored map `text` row is not automatically eligible: its `x,y` remain literal. Polygon and text units are independent snapshots, and matching `key`, `detail`, localized text, proximity, or layer order does not establish ownership. A future projected-region adapter must define an explicit one-label/one-compound-polygon association, retain the authored text coordinate as its preferred inside anchor, and specify even-odd containment, hole/island ownership, eligible outer boundary, outside-side order, segment ties, collision, clipping, and safe-area fallback before either client may move that label or create a leader. Once admitted, it must use the same inside-first, half-physical-pixel, exactly-once, pure-planner, complete-label, and atomic-fallback rules. A shape adapter may explicitly reuse the §11.4.1 natural-boundary, physical-side partition, side-local collision, and post-displacement leader-recomputation pipeline when its ownership and boundary geometry make those stages meaningful; the eligible ARC-family adapter is the current adapter that does so. Until a projected-region adapter defines otherwise, failure retains the authored text at its literal `x,y`; there is no ordinary guide to restore. Clients may not implement a map-only approximation based on a bounding box or centroid.

### 12. Security, resource, and accessibility requirements

Owner: [2d.md#12-security-resource-and-accessibility-requirements](../reference/charts/2d.md#12-security-resource-and-accessibility-requirements)

- `chart.limits.bounded-logging`: use bounded diagnostic logging without dumping complete payloads;
- `chart.limits.non-finite-geometry`: prevent non-finite geometry from reaching drawing APIs by applying the documented row-suppression or path-separator rule, and reject invalid literal style tokens;
- `chart.limits.no-executable-content`: avoid executable expressions, arbitrary field paths, URLs, embedded HTML, remote images, and platform-specific style objects;
- `chart.limits.checked-scan`: scan source geometry with overflow-checked indices and totals, cancellation checks, and bounded diagnostics. Projection must not reserve storage from an unchecked row/product count. A superseded scan stops promptly. If a platform allocation or scene-construction API reports a recoverable failure, the client follows the chart failure policy in §10 rather than predicting it with a portable hard-coded row number. Process-level out-of-memory termination is not recoverable on every V1 platform and is therefore not a semantic chart fallback guarantee;
- `chart.limits.culling-preserves-data`: apply the specified viewport culling and deterministic resampling without changing source domains, raw row identity, tooltip values, or accessible data;
- `chart.limits.stable-gestures`: preserve stable layout and exact terminal viewport state during gestures;
- `chart.limits.cancel-superseded`: cancel chart construction when its screen or source snapshot is superseded;
- `chart.limits.authorization`: preserve normal protocol authorization and request ownership;
- `chart.a11y.expose-localized`: expose localized chart, series, category, channel, and tooltip information to accessibility services;
- `chart.limits.preserve-response`: preserve the underlying response and follow the chart failure policy in §10 when its chart cannot be rendered.
- `chart.limits.reject-before-lowering`: Projection rejects a structurally over-limit chart before native lowering. Source geometry size alone is not structural invalidity. Renderers keep the source snapshot available for exact domains, interaction, and ordinary data, while allocating only the culled/resampled visible scene described above.

### 13. Cross-platform rendering contract

Owner: [2d.md#13-cross-platform-rendering-contract](../reference/charts/2d.md#13-cross-platform-rendering-contract)

- `chart.runtime.cross-platform-semantics`: Android and iOS may use different native drawing libraries, but they must share the semantic behavior defined here:
- `chart.render.shared-validation`: schema parsing and validation;
- `chart.render.shared-mark-lowering`: mark lowering (`bar` to `rect` where desired);
- `chart.render.shared-domains`: domain, scale, grouping, ordering, and missing-value rules;
- `chart.render.shared-polar-mapping`: equal-aspect polar mapping, circular clipping, and straight-chord geometry;
- `chart.render.shared-polygons`: polygon subpaths, implicit closure, `evenOdd` fill, and fill-before-stroke order;
- `chart.render.shared-polar-guides`: polar guide ownership and geometry, polar mark hit testing, and the absence of a polar viewport in version one;
- `chart.render.shared-palette`: deterministic category palette assignment;
- `chart.render.shared-composition`: composition traversal and layer order;
- `chart.render.shared-guides`: generic guide inference and explicit replacement, candidate identity, tick/label selection, typed localization, span components, layout order, and inert interaction semantics;
- `chart.render.shared-tooltips`: tooltip contents and source-row identity;
- `chart.render.shared-limits`: structural/metadata limits, visible-work policy, and fallback behavior.
- `chart.render.allowed-platform-differences`: Theme-native fonts, gesture feel, the fitting density tier selected under the normative collision algorithm, and exact pixel layout may differ. The complete candidate ladder, identities and ordering within each tier, priority rules, data meaning, series grouping, scale domains, and literal colors must not differ; the selected responsive tier may.

### Client-side 3D charting for Libertas protocol data

Owner: [3d.md#client-side-3d-charting-for-libertas-protocol-data](../reference/charts/3d.md#client-side-3d-charting-for-libertas-protocol-data)

- `chart3d.presentation.exact-versions`: Presentation uses the schema, exact dependency versions, and locale document associated with the returned value; unrelated installed versions do not redefine that value. See the [schema reference](../reference/schema.md).

### 1. Purpose and compatibility boundary

Owner: [3d.md#1-purpose-and-compatibility-boundary](../reference/charts/3d.md#1-purpose-and-compatibility-boundary)

- `chart3d.boundary.no-aliases`: clients never interpret `Chart3DChannel`, `Chart3DScale`, `Chart3DGuide`, or `Chart3DView` as aliases of their two-dimensional counterparts; and
- `chart3d.boundary.pre-3d-unchanged`: a schema without `Chart3D*` attributes has exactly its pre-3D behavior and final schema representation.

### 3.3 Protocol Union wrappers

Owner: [3d.md#33-protocol-union-wrappers](../reference/charts/3d.md#33-protocol-union-wrappers)

- `chart3d.data.presentation-root`: Clients never recursively search for an arbitrary annotated descendant and never choose among several chart fields. The effective selected variant or its one permitted direct chart field is the only presentation root. More than one visible chart field, one visible non-chart field beside a chart, or a mixture of `Chart` and `Chart3D` invalidates chart presentation.

### 4.3 Compositions

Owner: [3d.md#43-compositions](../reference/charts/3d.md#43-compositions)

- `chart3d.render.depth-ties`: Depth normally determines visibility. When two fragments have equal mapped depth, the later flattened child wins, then the later canonical row. A renderer may use a bounded render-only depth bias to implement that tie; it must not change data coordinates, scales, domains, tooltip values, hit identities, or requests. This rule makes server-emitted contour lines layered after a surface stable rather than subject to platform-specific z-fighting.
- `chart3d.facet.scenes`: `row` and `column` create facets. Every facet is an independent camera scene using the unit or enclosing layer's view. A layer may lower compatible children into one facet grid only when their ordered raw facet domains are identical; otherwise it uses independent grids or follows the chart failure policy in §12.6.

### 7.2 Components and inference

Owner: [3d.md#72-components-and-inference](../reference/charts/3d.md#72-components-and-inference)

- `chart3d.guide.axis-layout`: 3D position axes are placed on camera-visible plot-box edges by a deterministic client layout; the schema does not expose unstable edge or screen-side choices. `domain` is the axis line, `ticks` and `labels` are typed scale candidates, and `grid` extends them over the applicable plot-box faces. Appearance legends use the responsive full-screen orientation policy in Section 12.5; previews retain the inherited bottom-shell form. Facet headings remain attached to their pane grid in every orientation.

### 7.3 Position-guide cadence and thinning

Owner: [3d.md#73-position-guide-cadence-and-thinning](../reference/charts/3d.md#73-position-guide-cadence-and-thinning)

- `chart3d.cadence.ladder`: An automatic continuous x, y, or z `source=scale` guide—that is, one without `tickStep`—adopts the complete deterministic cadence contract in [Section 6.4.5 of the general chart specification](../reference/charts/2d.md#645-scale-ticks-labels-and-typed-formatting). Quantitative guides use its closed `{1,2,5} * 10^k` ladder, temporal guides use its closed typed ladder, and every probe retains its `4096`-candidate bound. The resolved domain endpoints bound an ordinary aligned tier but do not enter it unless that lattice generates their exact identity. If no ordinary tier wins, the priorities-plus-resolved-endpoints terminal tier follows. A nondegenerate automatic tier with only one candidate cannot win over that terminal endpoint pair; only a degenerate domain has one automatic raw candidate.
- `chart3d.cadence.settled-pitch`: Cadence selection occurs only after the camera, plot box, and viewport have settled. A quantitative guide projects every candidate onto the final camera-visible axis edge and selects the first ladder tier whose adjacent projected anchors meet the general fixed pitch: `64` logical points for a screen-horizontal edge, the greater of `40` logical points and `1.4` times the effective caption-font line height for a screen-vertical edge, or `40` logical points when labels are suppressed. For a quantitative transform, an edge-length-derived candidate count alone is not sufficient.
- `chart3d.cadence.temporal-probe`: A temporal guide instead derives its fixed-pitch capacity from that settled projected edge and uses the general ICU-free nominal-width probe. It constructs the exact UTC or local calendar run only once, after selecting the cadence. Calendar or daylight-saving variation may make that run denser than its nominal probe geometry; the selected-run thinning rule below handles the difference without selecting another cadence or replacing it with terminal endpoints. Neither probe performs localization, text formatting, shaping, wrapping, or measurement, and the temporal probe does not construct a calendar, resolve a time zone, or invoke ICU.
- `chart3d.cadence.one-plan`: One settled camera-and-viewport state owns one immutable cadence plan. Camera gesture frames transform that plan with the marks and do not iterate the ladder; a changed projected-geometry key selects a replacement only when the camera or viewport settles. Locale and final label width never select a cadence. After selection, the client constructs exactly one bounded visible candidate run and formats the complete run deterministically through the typed rules in the general contract, including its cadence-derived whole-run numeric precision guard. It never formats candidates one at a time or formats a rejected tier.
- `chart3d.cadence.thinning`: Final localized text can still require visual thinning. The client tests the complete anchored power-of-two tiers with strides `1,2,4,8,...`, anchored at candidate index zero and always retaining the last semantic candidate. It selects the first tier whose final projected label boxes fit with at least four logical points of separation. A removed candidate's enabled label, tick, and grid paint are removed atomically; retained accessibility text remains unabridged and exposes that exact selected semantic run rather than rebuilding a default-capacity run independently of the settled camera. This paint-only thinning never changes or reprobes the selected automatic cadence, never changes an authored `tickStep`, and never substitutes a different candidate ladder merely because fewer labels remain visible.

### 7.4 Other numeric guide runs

Owner: [3d.md#74-other-numeric-guide-runs](../reference/charts/3d.md#74-other-numeric-guide-runs)

- `chart3d.cadence.legend-run`: A continuous numeric appearance `source=scale` guide adopts the general continuous-legend cadence on its exact 160-logical-point rail. Candidate selection uses the applicable closed automatic ladder and its bounds before any localization or formatting. The client then formats the complete selected candidate run, including every retained essential endpoint, as one set. An appearance legend cannot derive its semantic cadence from an incidental plot axis or format each rail label independently.
- `chart3d.cadence.numeric-format`: Numeric `source=value` and `source=span` guides retain their authored row candidate identities and therefore do not invoke an automatic scale ladder. They still format each complete bounded candidate run as one set. For every 3D numeric guide run, a nonempty authored `Format` is authoritative. Otherwise the client uses the general cadence-derived whole-run precision rule: authored `tickStep`, selected automatic quantitative cadence, associated scale cadence, or the normalized smallest positive adjacent gap, in that order as applicable. It never starts from a generic 15-significant-digit default and never chooses precision independently per label. The declared value remains in its authored `PhysicalUnit`; after the one numeric formatting pass, the client attaches that provided unit with the general CLDR rule and performs neither automatic unit conversion nor a second rounding pass. A client must not use a per-value platform-default number conversion for visible or accessibility guide text.

### 8.2 Camera semantics

Owner: [3d.md#82-camera-semantics](../reference/charts/3d.md#82-camera-semantics)

- `chart3d.view.poles`: `azimuth` rotates around the positive-z axis and `elevation` moves above or below the x/y plane. Before evaluating the camera, implementations clamp the effective elevation to the closed `[-90,90]` degree interval. The exact poles are valid camera states; clients do not substitute an epsilon such as `89` or `89.9`. At `+90` or `-90`, the client retains authored or transient azimuth and selects the same deterministic projected-positive-y, then positive-x, camera-up fallback on every platform. Continuing an outward vertical orbit at a pole leaves elevation at that pole. Reversing direction moves away immediately without hysteresis, pole reflection, a 180-degree azimuth change, or camera roll.
- `chart3d.view.capability-gates`: When `orbit=true`, a `view.orbit` action updates only transient camera azimuth and elevation, clamping the resulting elevation to `[-90,90]`. When `zoomEnabled=true`, a `view.zoom` action updates transient positive zoom without a fixed schema-level cap. When `pan=true`, a `view.pan` action updates the transient target in normalized plot space. `orbit=false` fixes rotation; `pan=false` fixes the target; `zoomEnabled=false` disables every user zoom action while retaining the authored initial `zoom`. These three properties are independent capability gates, not a selected interaction mode. They do not change projection or initial camera meaning. `view.reset` returns the complete camera to the effective authored/default view, including its clamped effective elevation. Section 12 defines the one universal mapping from touch, visible controls, keyboard input, and accessibility actions to this vocabulary.
- `chart3d.view.camera-presentation-only`: Camera motion never alters scale domains, topology, source identity, tooltip values, accessibility order, or request data.

### 8.3 Universal client action vocabulary

Owner: [3d.md#83-universal-client-action-vocabulary](../reference/charts/3d.md#83-universal-client-action-vocabulary)

- `chart3d.actions.vocabulary`: Every Android and iOS renderer exposes the following semantic actions. The names are normative for cross-platform fixtures and localization mapping, but they are client-internal: they are not schema attributes, protocol messages, or serialized camera data. Every datum or camera action also receives its explicit physical-scene identity; it never resolves an implicit last-touched scene.
- `chart3d.actions.steps`: A discrete orbit step is `15` degrees and clamps its resulting elevation to `[-90,90]`. A discrete pan step inverse-projects a screen displacement equal to `10` percent of the physical scene viewport width or height at the current camera-target plane, using the deterministic camera-right/up basis from Section 8.2, then moves the visible scene in the requested screen direction. A discrete zoom-in step multiplies zoom by `1.25`; zoom-out divides by `1.25`. A client stops before producing a non-positive or non-finite zoom or any other non-finite camera value. Continuous gesture sensitivity and damping may remain platform-native, but direction and action meaning cannot differ.
- `chart3d.actions.labels`: Client UI uses localized, nontechnical wording: **Rotate view**, **Move view**, **Zoom in**, **Zoom out**, **Reset view**, **Explore 3D chart**, **Back**, **Next data point**, and **Previous data point**. `orbit` and `pan` remain schema and internal terms and are not exposed as untranslated user-facing labels.

### 9.3 Cells

Owner: [3d.md#93-cells](../reference/charts/3d.md#93-cells)

- `chart3d.cells.triangulation`: A cell exists only when all four vertices are present and have finite mapped x/y/z. `surface` triangulates it as `(a,b,c)` and `(a,c,d)`, fixing the diagonal across platforms. Faces are visible from either side; winding does not remove data. `wireframe` draws only the four grid edges and never exposes the internal triangulation diagonal.
- `chart3d.cells.lookup`: Implementations find cells through checked row/index lookup. They do not allocate an unchecked `u-count * v-count` matrix. Repeated edges are painted once per grid group. Missing or invalid vertices remove incident cells and break wire paths.

### 11. Localization and visible text

Owner: [3d.md#11-localization-and-visible-text](../reference/charts/3d.md#11-localization-and-visible-text)

- `chart3d.l10n.schema-only`: Geometry, topology, grouping, scales, keys, and interaction identities use only schema and raw values. The existing localized package document supplies optional presentation:

  | Existing source | 3D use |
  | --- | --- |
  | Type `DisplayName` and `Description` | Scene title, help, accessibility context |
  | Field/member localized `Name` and `Description` | Axis/legend/tooltip captions and help |
  | Enumeration member document | Localized category label and help |
  | Libertas object resolver | Dynamic names for Device, VirtualDevice, User, Endpoint, or LanDevice identities |
  | Package `StringResources` | Render `FormattedText` Bytes through LMF1 |
  | Client localization catalog | Section 8.3 control terms, errors, fallback, camera actions, and accessibility announcement grammar |

### 12.1 Inspection and protocol boundary

Owner: [3d.md#121-inspection-and-protocol-boundary](../reference/charts/3d.md#121-inspection-and-protocol-boundary)

- `chart3d.interaction.hit-test`: Hit testing uses final visible, depth-tested geometry. The nearest visible fragment wins; equal-depth ties use flattened layer and row order. A line or wire edge resolves to one of its original endpoint rows. A surface cell resolves to its nearest contributing source vertex. The renderer never synthesizes a request row.

### 12.2 Preview and full-screen ownership

Owner: [3d.md#122-preview-and-full-screen-ownership](../reference/charts/3d.md#122-preview-and-full-screen-ownership)

- `chart3d.preview.gesture-neutral`: The application-shell rules in [Runtime endpoint presentation](../reference/endpoint-presentation.md) apply to both 2D and 3D charts. A 3D preview is a live but gesture-neutral summary. Its plot registers no inspection, orbit, pan, pinch, zoom, double-tap, or long-press recognizer. A tap on the preview or its localized **Explore 3D chart** action invokes `fullscreen.open` and always pushes full-screen Chart Explorer. Ordinary touches that begin over the preview therefore remain available to the containing workspace scroll.
- `chart3d.explorer.ownership`: Only full-screen Chart Explorer owns direct 3D manipulation, inspection, tooltips, and camera controls. The application shell's normal Back affordance invokes `fullscreen.close`, pops exactly that chart destination, and reveals the retained previous chart or workspace. It is not a second chart control or a separate Close button. There is no engaged inline presentation, interaction mode, or mode variable.
- `chart3d.explorer.scene-pointer`: Within Chart Explorer, a pointer sequence belongs to the physical scene in whose final plot bounds it begins. Concat children and facets remain independent scenes; the gesture changes only that scene's transient camera. A layer has one physical scene and therefore one camera. Touches outside a scene remain available to the Chart Explorer shell and its outer scroller.

### 12.3 Required direct-touch bindings

Owner: [3d.md#123-required-direct-touch-bindings](../reference/charts/3d.md#123-required-direct-touch-bindings)

- `chart3d.touch.bindings`: With screen-reader touch exploration inactive, clients use exactly this V1 mapping inside a full-screen physical scene:

  | Touch input | Semantic action |
  | --- | --- |
  | Tap visible eligible geometry | `inspect.at` for the nearest depth-tested datum. |
  | Tap empty scene space | `inspect.clear`. |
  | One-finger drag | `view.orbit`; the visible scene follows the drag until elevation reaches its closed pole. |
  | Two-finger centroid translation | `view.pan`; the visible scene follows the centroid. |
  | Pinch or spread | `view.zoom`; pinch reduces and spread increases zoom about the current target. |
  | Simultaneous two-finger translation and span change | Apply enabled `view.pan` and `view.zoom` components together. |

- `chart3d.touch.separation`: One-finger orbit never changes the target or zoom. Two-finger translation never changes azimuth/elevation. Pinch/spread never changes the target, so an off-center pinch cannot bypass `pan=false`. The two-finger twist component is ignored because V1 has no camera roll.
- `chart3d.touch.no-other-gestures`: Double tap, touch-and-hold, edge-origin gestures, and gestures with three or more fingers have no Chart3D V1 action. In particular, double tap never resets the camera, and a client dispatches a recognized single tap without delaying it to await a possible second tap. Touch-and-hold may acquire a future ordinary context-menu meaning, but it cannot be required for inspection or camera control. Operating-system navigation and assistive-technology gestures take priority and clients do not install system-gesture exclusion regions for a chart. A renderer-library default that assigns any of these inputs differently must be overridden.

### 12.4 Gesture arbitration and continuity

Owner: [3d.md#124-gesture-arbitration-and-continuity](../reference/charts/3d.md#124-gesture-arbitration-and-continuity)

- `chart3d.gesture.arbitration`: Clients use native touch slop and gesture recognizers and do not capture a sequence merely because its first pointer went down. Control hit targets, assistive technology, and system-edge navigation resolve before chart gestures. For a sequence that reaches the chart:
- `chart3d.gesture.slop`: exceeding touch slop cancels a pending tap before camera movement begins;
- `chart3d.gesture.second-pointer`: the arrival of a second pointer atomically ends one-finger orbit, cancels a pending tap, and establishes a new centroid and span without a camera jump;
- `chart3d.gesture.rebase`: any pointer-count change rebases the remaining two-finger transform; after a two-finger transform falls back to one pointer, orbit does not resume until every pointer has lifted and a new sequence begins;
- `chart3d.gesture.third-pointer`: the arrival of a third pointer cancels chart recognition without changing the last finite camera state, and recognition cannot restart until every pointer has lifted;
- `chart3d.gesture.disabled-capability`: `orbit=false` ignores and does not consume one-finger drag rather than remapping it to pan; `pan=false` ignores the translation component without consuming or remapping it; and `zoomEnabled=false` ignores the span-change component without consuming or remapping it;
- `chart3d.gesture.both-disabled`: a sequence with both pan and zoom disabled remains available to an eligible ancestor recognizer; and
- `chart3d.gesture.cancellation`: cancellation or interruption commits the last finite camera state, produces no inertia after cancellation, and never resets the view or emits a request.
- `chart3d.gesture.inertia`: Native damping or post-release inertia is allowed for an otherwise completed orbit or pan, remains presentation-only, stops on the next input, and is disabled when the platform's reduced-motion preference is active. Reset uses an immediate or short non-spatial transition under reduced motion. Auto-rotation remains excluded.

### 12.5 Visible and accessible controls

Owner: [3d.md#125-visible-and-accessible-controls](../reference/charts/3d.md#125-visible-and-accessible-controls)

- `chart3d.controls.scene-local`: Preview exposes only its Configure action when otherwise applicable and the action that opens Chart Explorer. Each independently interactive physical scene owns one scene-local control set in full-screen Chart Explorer; a root, concat, or facet control never silently targets the last-touched scene. Under the rules in Section 4.3, an unfaceted unit or layer has one scene, while every resolved facet is a separate scene. For a root with one physical scene, its scene-local chrome is also the root chrome.
- `chart3d.controls.viewport-class`: Full-screen scene chrome responds to the current usable viewport after safe areas, system bars, and persistent application chrome. Let its finite positive width and height be `W,H`. `H > W` is portrait; `H <= W` is landscape or square. Clients use this measured viewport classification rather than device orientation notifications, the assigned plot-box aspect ratio, or a stale pre-rotation size. Rotation or window resizing re-evaluates only placement; it does not recreate the physical scene, reset its camera or inspection, replace its settled guide semantics, or reset either outer scroll position.
- `chart3d.controls.portrait`: In portrait, one full-width scene-local control strip immediately precedes the physical scene. The client first attempts one complete row. If it does not fit, the same fixed-order controls remain in one conventional horizontal control strip; it scrolls only on its control axis and does not wrap into multiple tall rows. A directional group remains intact and no control is hidden behind a mode, selector, disclosure, or modal surface. The physical scene follows that strip at its assigned size. Its appearance and detail guides follow the scene in the inherited full-width bottom form.
- `chart3d.controls.landscape-rail`: In landscape or square, the client first attempts one trailing scene-local utility rail beside the physical scene. The rail contains the complete control set first. Below the controls, complete appearance and detail guide blocks use their ordinary horizontal form and greedily pack in semantic order from the physical leading edge to the trailing edge, then wrap downward. The controls retain their fixed reading order and wrap only between complete groups. Position guides remain on their deterministic camera-visible plot-box edges, and facet row/column headings remain attached to their pane grid; neither enters the utility rail.
- `chart3d.controls.rail-admission`: The client measures the localized controls-width rail with the active accessibility font and platform touch targets. The controls may use that rail when the remaining physical scene retains a finite positive viewport and satisfies the existing `M = min(200,C)` final post-guide plot-width rule, where `C` is the plot frame remaining after the utility rail and its gap have been removed. A positive `C < 200` is valid when the final plot retains all of `C`. Guides use the same right rail whenever it can contain at least one complete localized guide block without clipping, shrinking or eliding it. Only when it cannot fit one complete guide do the guides alone fall back to their full-width horizontal flow below the physical-scene/control row; admitted controls remain in the trailing rail. In either placement, guide height never reduces the physical scene's normally assigned full-screen height. Overflow increases the one outer vertical Chart Explorer scroll extent; it never creates a private guide scroller or overlays the scene. Concat children and facets apply this decision independently to their own final pane width and scene-local ownership.
- `chart3d.controls.order`: Each scene's safe control area contains, in fixed reading order when applicable: the **Rotate view** directional group, the **Move view** directional group, **Zoom out**, **Reset view**, then **Zoom in**. Each directional group contains explicit left, right, up, and down buttons mapped to its corresponding `.step` action. A disabled capability removes its complete directional or zoom group and leaves no empty slot. **Reset view** is always present and remains operable as an idempotent action while the transient camera already equals the effective authored/default view. The application shell's Back affordance remains outside every scene-local control set.
- `chart3d.controls.touch-targets`: Every visible control has a minimum touch target of `44 x 44` points on iOS and `48 x 48` density-independent pixels on Android and remains inside platform safe and system-gesture insets. Controls and guides never overlay the physical scene. They do not reduce its assigned height; only an admitted landscape utility rail may consume its cross-axis width after the required post-guide plot width has been proven.
- `chart3d.controls.no-selector`: The directional groups are directly visible tap-only controls. A client never replaces them with a Rotate/Move selector, disclosure, modal surface, or hidden last-used action, so they add no control mode or presentation state. Direction labels describe movement of the visible scene on screen, not internal camera motion.
- `chart3d.a11y.rotate-actions`: Rotate view left, right, up, and down when `orbit=true`;
- `chart3d.a11y.move-actions`: Move view left, right, up, and down when `pan=true`;
- `chart3d.a11y.zoom-actions`: Zoom in and Zoom out when `zoomEnabled=true`;
- `chart3d.a11y.reset-action`: Reset view for the explicitly identified physical scene, including while it is already at its effective authored/default view;
- `chart3d.a11y.datum-traversal-actions`: Next data point and Previous data point for virtualized source traversal; and
- `chart3d.a11y.explore-back`: Explore 3D chart in preview and the normal Back action in Chart Explorer.
- `chart3d.a11y.equivalent-input`: Screen readers own their normal touch gestures; direct-touch passthrough is not the only way to operate a chart. Visible buttons and accessibility actions call the same semantic action implementation, and switch control, voice control, keyboard, and pointer activation remain equivalent. Keyboard operation uses the focused named controls and introduces no hidden shortcut or mode. Wheel or trackpad magnification over a physical scene invokes `view.zoom` when enabled.
- `chart3d.a11y.announcement`: After a discrete camera action, or after a continuous gesture and any permitted inertia have reached their final settled state, the client announces exactly one bounded localized summary rather than announcing every animation frame. The summary identifies the physical scene and contains projection, azimuth, elevation, and zoom. When pan changed the target, the same completion announcement includes its normalized x/y/z target as locale-formatted percentages. Angle values are rounded to the nearest degree and zoom is shown with at most two locale-formatted fractional digits.
- `chart3d.a11y.descriptions`: localized scene, series, facet, axis, and tooltip descriptions;
- `chart3d.a11y.row-traversal`: a virtualized traversal of eligible source rows independent of occlusion;
- `chart3d.a11y.typed-values`: raw typed values formatted through the same locale, Format, PhysicalUnit, object resolver, and LMF1 pipeline as visible output;
- `chart3d.a11y.nonvisual`: a nonvisual indication for every meaning otherwise encoded through paint, size, shape, or depth.

### 13. Security and resources

Owner: [3d.md#13-security-and-resources](../reference/charts/3d.md#13-security-and-resources)

- `chart3d.limits.cancellation`: A superseded source, schema, locale, size, or camera query cancels pending work. Visible level of detail does not change source domains, tooltips, requests, accessibility, or the ordinary data view. Non-finite geometry is suppressed; recoverable allocation or rendering failures follow the chart failure policy in §12.6. Diagnostics remain bounded and do not dump complete payloads.

### 15. Cross-platform rendering and Hub behavior

Owner: [3d.md#15-cross-platform-rendering-and-hub-behavior](../reference/charts/3d.md#15-cross-platform-rendering-and-hub-behavior)

- `chart3d.render.shared-semantics`: Android and iOS may use different native graphics libraries, but they share:
- `chart3d.render.may-differ`: Theme-native fonts, antialiasing, lighting that carries no data meaning, continuous gesture sensitivity, damping, and permitted inertia may differ. Gesture-to-action assignment, visible-scene direction, discrete step sizes, capability gating, control meaning, geometry, scale meaning, topology, identity, literal colors, camera defaults, and supported/unsupported classification may not differ.

### Standard properties

Owner: [documentation.md#standard-properties](../reference/documentation.md#standard-properties)

- `doc.initial-text.persisted-priority`: Persisted or user-assigned text has absolute priority and is immutable to programmatic defaulting. Clients must not replace it during locale, document, or schema refresh, migration, upgrade, or any later default application. This includes a deliberately empty saved value or task name.

### Endpoint list with displayed subscription data

Owner: [endpoint-presentation.md#endpoint-list-with-displayed-subscription-data](../reference/endpoint-presentation.md#endpoint-list-with-displayed-subscription-data)

- `endpoint.list.header-drawer`: Tapping its header opens a request drawer. Use the displayed value's legal NextRequest choices in their declared order, with existing access, ExcludeUI, applicable ConstrainedBy, and [LinkOnly](../reference/protocols.md#link-only) filtering. Construct the selected request using the originating interaction's [reference context](../reference/protocols.md#interaction-reference-context). Show the drawer even for one choice; with no legal choices the header has no request action.
- `endpoint.header.no-duplicate-subscription`: Do not reissue the default subscription or stack a duplicate data screen merely because the endpoint header was tapped. This is action selection on an existing destination, not initial endpoint entry. An endpoint with no displayed data and a sole endpoint opened directly still follow the default-entry rules. Selecting a drawer action retains the normal composer, reply, chart, and Back behavior.

### Endpoint Workspace

Owner: [endpoint-presentation.md#endpoint-workspace](../reference/endpoint-presentation.md#endpoint-workspace)

- `endpoint.navigation.native-back`: The workspace owns one vertical content scroll and a persistent title/back bar. Because workspaces, previews, Chart Explorer, protocol data, and request composers are navigation destinations, every full-height destination uses the normal back affordance; [stacked-link pages](../reference/protocols.md#links) additionally require their system Close. Its section order is invariant:
- `endpoint.workspace.fixed-in-task-configuration`: This request-payload rule does not reinterpret `Fixed` on a client Endpoint in task configuration: there it constrains the peer task process while the Endpoint-ID selector remains editable across all schema-compatible instances.
- `endpoint.preview.failure-retains-workspace`: If a default chart cannot be rendered, its preview follows the shared [chart failure policy](../reference/charts/2d.md#10-validation-and-fallback). The implementation may provide an ordinary-data fallback or display an error message. Preserve the endpoint workspace and its response ownership. The same choice applies to Chart Explorer; an implementation may return a failed Explorer to its retained preview and present the fallback or error there. Presentation failure never closes the workspace or reissues the request.
- `endpoint.preview.direct-explorer`: Configure pushes a separate request-composer screen above the retained chart preview workspace. On a successful chart response, remove only the composer and push Chart Explorer above the retained workspace. Do not replace the preview with ProtocolData or insert a ProtocolData destination between the preview and Explorer. Back returns to the same preview workspace. This applies to both 2D and 3D charts on Android and iOS. Cancelling the composer leaves the workspace intact; an error preserves the composer for retry without changing the preview. The protocol transaction stack below governs protocol-data interactions, not this preview-originated chart customization path.
- `endpoint.workspace.preview-context`: Each automatic preview owns its [reference context](../reference/protocols.md#interaction-reference-context). Configure uses that exact preview's context for `CopyFrom` and supported external references under the [shared path contract](../reference/schema.md#context-and-syntax), including its retained successful Response and current SubscriptionData when subscribed. Do not substitute a sibling preview's data or the endpoint value that originally opened the chooser after this preview receives its own data. Before it receives any data, retain the chooser's originating reference context when available; otherwise ordinary initialization with an empty context applies.
- `endpoint.workspace.explicit-close`: The workspace's vertical scroll therefore has one owner on every pointer path, including paths that begin over a chart preview. Closing the workspace is an explicit navigation action; downward content overscroll never closes it.

### Protocol transaction stack

Owner: [endpoint-presentation.md#protocol-transaction-stack](../reference/endpoint-presentation.md#protocol-transaction-stack)

- `endpoint.composer.pending-reply-owner`: A request opened from an existing screen is a **transient request composer** stacked above that screen, including a request selected from an endpoint action list. Cancelling before submission removes only the composer and preserves the originating screen, its data, and its reference context. It does not run landing navigation. While submission is pending, the composer remains the reply owner and cannot be dismissed through native Back; [exit while an operation is pending](../reference/protocols.md#exit-while-pending) states the one exit. An Error keeps it available for correction and retry; a non-transition acknowledgement removes only the composer; a transitioning reply installs or updates the response destination as described below. Subscription updates may continue to refresh their owning screen underneath the composer. An ordinary [link](../reference/protocols.md#links) opens this composer when its target has editable input; above a stable editor its plain reply is applied to that editor as a [return](../reference/protocols.md#link-return) rather than an acknowledgement.
- `endpoint.composer.no-workflow-back`: A composer never offers a workflow Back control, even when its Request declares `PrevRequest`; its Cancel closes it while idle ([previous actions](../reference/protocols.md#submit-previous-actions-and-completion)).
- `endpoint.actions.validate-before-send`: Requests come from the displayed variant's effective `NextRequest`, preserving declared order and existing visibility/access, ConstrainedBy, and LinkOnly filtering, narrowed to an explicit `Actions` array when the page supplies one; a page may also declare further [named action controls](../reference/protocols.md#named-action-controls). Promoting a child changes presentation only; request resolution still belongs to the enclosing protocol variant. Follow the [Actions selection and input rules](../reference/protocols.md#single-action-confirmation-and-input-presentation): hide the button with no choice; tapping it selects a sole choice directly or opens the chooser for several. Selection sends an input-free request after validation and any required confirmation, or opens editable input and waits.
- `endpoint.actions.missing-next-request`: Missing `NextRequest` never implies every request in the protocol.
- `endpoint.actions.showing-never-sends`: The underlying ProtocolData screen remains intact while choosing or composing. Cancelling returns to that same screen. Selection and composition preserve its displayed data and subscription ownership until the ordinary reply handling applies. Showing or relocating Actions never sends a request. A transitioning action reply belongs to its working interaction, even when it uses the default subscription's variant. Never use variant equality to replace the retained subscription landing or pop back to it; actual subscription deliveries continue to update their owning snapshot.
- `endpoint.acknowledgement`: For a regular action above a displayed value, outside the reply to the landing entry request itself, SubscriptionRequest completion, a stacked-link completion under [Links](../reference/protocols.md#links), and a master-detail [refresh](../reference/presentation.md#master-detail-panes), a normal correlated Response with neither `WorkflowRequest` nor a declared `NextRequest` is instead a non-transition acknowledgement. It completes the in-flight attempt but does not install a response destination, replace the displayed protocol value, change the current choices, or interpret every protocol Request as a choice. This is the ordinary shape for reusable `Success`/`Failure` results. Transient request/wait presentation may finish, but the protocol destination below it remains unchanged. Reference context follows the [reference context rule](../reference/protocols.md#interaction-reference-context).
- `endpoint.subscription-start`: A request invoked as SubscriptionRequest starts a subscription. The operation selected under the shared role rule, not the response variant, determines this. Later live data updates each eligible interaction associated with that endpoint and compatible response type; it does not become an unrelated interaction's reference context. `NextResponse`, when present, restricts the eligible response variants. An acknowledgement leaves the displayed state and subscription active; later matching live data can install or update that interaction's SubscriptionData snapshot under the shared reference context rule.
- `endpoint.subscription-release`: On Hubs with explicit subscription release support (image version 20260901 or later), leaving the last interactive screen for an explicit endpoint subscription stops that client's explicit subscriptions for the endpoint. Older Hubs release them when the Control connection closes. The endpoint-wide scope can affect another explicit subscription for the same client and endpoint; it does not stop the automatic default subscription. Reopen the interaction when you need to subscribe again.

### Workflow editor destinations

Owner: [endpoint-presentation.md#workflow-editor-destinations](../reference/endpoint-presentation.md#workflow-editor-destinations)

- `endpoint.workflow.one-editor`: Keep one active workflow editor destination; each successful step replaces it instead of accumulating historical editors. Retain an actual subscription landing beneath the flow; a non-subscription flow replaces its current stable working page. No cookie response is displayed between steps. A completed flow replaces its editor with the final response, including responses with no NextRequest.
- `workflow.navigation.no-history`: Display explicit Next/Submit in every workflow editor. Outside an embedded master-detail trailing pane, also offer eligible workflow Back actions. Back opens the ordered previous-action chooser only when several eligible actions exist; Next submits the current editor. Entry through either workflow path always waits for user submission. Native screen Back/dismissal exits the flow; nested-editor and picker dismissal return to the same parent editor. Those local movements must not invoke PrevRequest or restore historical workflow drafts.
- `workflow.navigation.row-reserves-space`: The workflow controls share one compact, persistent action row outside the scrolling form: outside an embedded trailing pane, **Go back** is a secondary action at the leading edge when an eligible previous action exists, and **Next** is the primary action at the trailing edge. Multiple previous actions retain their ordered chooser. The row reserves its own space; it never covers fields or validation feedback. The form uses the remaining viewport and can scroll its last field and error fully into view. On Android and iOS, hide the whole action row while the software keyboard is visible, including in embedded panes; restore it when the keyboard is dismissed. Showing or dismissing the keyboard never submits or sends a previous action. The working surface accounts for the keyboard and system safe areas once, including when the editor is an embedded pane. Text remains readable and controls remain usable with enlarged text and localized labels. In an embedded master-detail pane, the row offers **Close + Next** and suppresses all workflow previous-action controls, including their chooser and shortcuts, under the [pane rules](../reference/presentation.md#master-detail-panes). The leading pane owns navigation for the whole arrangement, and the App must provide all required routes there. The pane rules also own restoration of ordinary previous actions when the editor becomes a full page, including after narrowing the window. Local picker and nested-field-editor dismissal retain their normal behavior.
- `endpoint.workflow.next-uniform`: Next retains the same submission behavior on every page. Clients do not infer a final step or change it to a special Submit action from the content, variant names or possible replies; the service selects the result under the [workflow contract](../reference/protocols.md#submit-previous-actions-and-completion).

### Client provider account

Owner: [llm-client-requests.md#client-provider-account](../reference/llm-client-requests.md#client-provider-account)

- `ai.provider.client-direct-routing`: **Client configuration** on Android and iOS can hold one LLM provider account: the provider, an API key, a model, and a base URL for an OpenAI-compatible provider. While one is configured, that client sends AI Import and AI Search requests directly to the provider and never to a Hub, and both features are available on every modern Hub it connects to (a legacy Hub has no App that declares them). A failed provider request is shown to the person; the client does not retry through the Hub. Without one, the connected Hub's connector serves both features as before.
- `ai.provider.key-device-only`: The key stays on the device in the platform's secure storage (Android Keystore, iOS Keychain, this device only). It is never sent to a Hub, logged, or included in a backup or diagnostic. Removing the account deletes it.

### Limits and checks

Owner: [llm-client-requests.md#limits-and-checks](../reference/llm-client-requests.md#limits-and-checks)

- `ai.provider.request-size-limits`: AI Import: at most 8 documents and 8 MiB in total, each PDF, PNG, JPEG or WEBP, recognized by its content and not only its declared type. The instruction must be nonempty and has no separate size limit; the validation JSON and the schema are at most 64 KiB each.
- `ai.provider.reply-envelope`: An AI Import reply must be the envelope: `ok: true` with a non-null `result`, or `ok: false` with an `error`. An AI Search reply must name only offered identifiers, each once.

### Provider mapping

Owner: [llm-client-requests.md#provider-mapping](../reference/llm-client-requests.md#provider-mapping)

- `ai.provider.gemini-mapping`:

  | Provider | Request | Documents | Structured output | Reply text |
  | --- | --- | --- | --- | --- |
  | Gemini | `POST https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent`; system text as `systemInstruction` | `inline_data` parts, base64 | `generationConfig.responseMimeType: application/json` and `responseJsonSchema` | The first candidate's text parts, which must finish with `STOP` |

- `ai.provider.claude-mapping`:

  | Provider | Request | Documents | Structured output | Reply text |
  | --- | --- | --- | --- | --- |
  | Claude | `POST https://api.anthropic.com/v1/messages`, `anthropic-version: 2023-06-01`; system text as `system` | `document` (PDF) and `image` content blocks, base64 | One tool named `document_transform` or `navigation` whose `input_schema` is the output schema, required by `tool_choice` | The forced `tool_use` block's `input` |

### LLM document transformation

Owner: [llm-document-transformation.md#llm-document-transformation](../reference/llm-document-transformation.md#llm-document-transformation)

- `doctrans.admission.connector-support`: Clients offer the action on modern Hubs and hide it on legacy Hubs. The [Control AI availability rule](../reference/protocols.md#ai-navigation-and-ai-search) applies before choosing documents or transmitting a transformation: an unavailable action reports an immediate error. The Hub must handle the document transformation messages and have an OpenAI or Gemini connector whose selected model accepts the supplied documents and structured output schema. Available AI does not guarantee those request-specific capabilities; ordinary errors and timeouts still apply after a request is sent.

### Request

Owner: [llm-document-transformation.md#request](../reference/llm-document-transformation.md#request)

- `doctrans.request.nonempty-instruction`:

  | Field | Type | Meaning |
  | --- | --- | --- |
  | `_instruction` | string | Required, nonempty client task instruction. No separate instruction-size limit. |

- `doctrans.request.validation-object`:

  | Field | Type | Meaning |
  | --- | --- | --- |
  | `_validationJson` | string containing a JSON object | Facts the documents must confirm. Use `"{}"` when there are no facts. At most 64 KiB. |

- `doctrans.request.facts-versus-shape`: The task instruction says **what** to extract or transform. The validation JSON supplies values that must be checked against the documents. The schema says **which JSON shape** the result must have. These are separate fields; clients do not interpolate placeholder tags into the instruction.
- `doctrans.extraction.no-guessing`: > Read the attached employment record. Extract the employee name exactly as > shown. Do not guess missing or illegible values.
- `doctrans.transaction.exact-64bit`: `_validationJson` and `_outputSchema` are serialized **strings** in the Control message. The examples above show the JSON values before string serialization. The client must preserve the 64-bit transaction ID without rounding it through a floating-point number.
- `doctrans.request.validate-json-objects`: The Hub requires `_validationJson` and `_outputSchema` to parse as JSON objects. It supplies the schema through the configured provider's structured-output mode: OpenAI uses `json_schema` with `strict: true`, and Gemini uses a schema-constrained JSON response format. OpenAI requires every object property in `required` and `additionalProperties: false`; represent a genuinely Nullable property with a required `type: [T, "null"]`. A schema outside a provider's supported subset can cause a provider error. The Hub does not interpret JSON Schema keywords or enforce the schema against the returned result. The client must check a successful result against its supplied schema before using it.

### App Schema authoring

Owner: [llm-document-transformation.md#app-schema-authoring](../reference/llm-document-transformation.md#app-schema-authoring)

- `doctrans.action.unavailable-contexts`: The action is unavailable in internal calculation requests and inside Hidden, Fixed, CacheIdentity, or ReadOnly subtrees. ReadOnly on a List restricts its membership, not editing an existing item.
- `doctrans.result.required-unavailable`: A selected composite may contain them: omit those properties from the generated output schema and preserve their normal values when updating the existing record. New List rows or Union payloads receive them through normal field initialization, such as Fixed, Default, or the existing construction rules. If a required value remains unavailable, normal validation rejects the proposal, except for the unmarked payload fields of a newly chosen Union variant, described above. The model never supplies it. ReadOnly results retain their normal visible presentation. For an existing Table or the same Union variant, omitted fields keep their current typed values, including visible fields that were outside the result schema. A newly selected Union variant initializes omitted payload fields by the normal construction rules.
- `doctrans.list.new-rows`: An extracted List creates new rows through normal initialization; it must not copy Hidden identities from old rows by position.

### Blank values and output types

Owner: [llm-document-transformation.md#blank-values-and-output-types](../reference/llm-document-transformation.md#blank-values-and-output-types)

- `doctrans.schema.selected-properties-required`: Every selected result property, including each selected property in a nested Table, is required in the generated strict output JSON Schema; Hidden and Fixed subtrees are omitted. A List is a JSON array whose item schema is derived recursively. Every Table object has exactly its selected children and `additionalProperties: false`; unselected children do not appear in the model output. The client checks List length against its Schema bounds, converts every item, and replaces the selected List as one typed subtree. It merges selected Table fields into the existing Table or same Union variant as one editor change.

### Nullable fields with no permitted choice

Owner: [llm-document-transformation.md#nullable-fields-with-no-permitted-choice](../reference/llm-document-transformation.md#nullable-fields-with-no-permitted-choice)

- `doctrans.choices.empty-permitted-set`: When the current constraints leave a Nullable Union, Enumeration, identity, or EnumSource field with an empty permitted set, its result schema admits exactly `null` (the model is offered only `null`) and the import proceeds; any other value is rejected. A non-Nullable field with an empty set refuses the action.

### Declared blank value

Owner: [llm-document-transformation.md#declared-blank-value](../reference/llm-document-transformation.md#declared-blank-value)

- `doctrans.schema.exclude-blank-default`: A field that accepts `""`, through `allowEmpty=true` or `blank`, adds it to its output schema: exact `""` joins an `enum` (Enumeration, variant-only Union, EnumSource, and permitted choices), a Boolean is JSON Boolean or `""`, a String drops its nonempty requirement, a Number uses its string form, and a Union with a marked payload field accepts `""` in place of its `variant`/`value` wrapper. The schema sent to the model never exposes the blank value or the Default. The client applies `""` exactly as if the user had typed or chosen that value. On a Union it chooses the blank variant (the Default variant under `allowEmpty=true`) as a manual choice does: the current variant keeps its payload, and a different variant initializes its payload by the normal construction rules, with required payload fields left unanswered and deferred to Submit as described for a [different variant](../reference/llm-document-transformation.md#app-schema-authoring).
- `doctrans.convert.strict`: The client accepts numeric text only when the entire string is a JSON number lexeme, with no surrounding whitespace, grouping separators, currency sign, `NaN`, or infinity, and checks the destination type and bounds. Conversion failure is an error; it never applies the Default. A whitespace-only String is invalid. The client converts every supported scalar, Table, List item, and declared null into the destination's Libertas type, checks the generated JSON schema, then runs normal typed field and cross-field validation before offering any change. It rejects any partial, extra, mismatched, or unconvertible result.

### Complete JSON projection and client conversion

Owner: [llm-document-transformation.md#complete-json-projection-and-client-conversion](../reference/llm-document-transformation.md#complete-json-projection-and-client-conversion)

- `doctrans.conversion.ordinary-semantic-codecs`: JSON Schema describes the JSON representation. The client converts that representation into the destination's actual Libertas type with its ordinary value codecs and validation rules; JSON primitive types do not replace the App's semantic types.
- `doctrans.conversion.number-exactness`:

  | Libertas value | Model JSON and client conversion |
  | --- | --- |
  | Number | Decimal numeric string when blank conversion or exact wide-integer representation requires it; otherwise JSON integer/number. Parse the complete value, preserve integer precision, and enforce native width, range, and step. |

- `doctrans.conversion.money-precision`:

  | Libertas value | Model JSON and client conversion |
  | --- | --- |
  | Money | Decimal major-unit string in the declared currency; convert exactly to integer minor units using the declared scale. Reject excess fractional precision and overflow. |

- `doctrans.conversion.enumeration-values`:

  | Libertas value | Model JSON and client conversion |
  | --- | --- |
  | Dynamic EnumSource | Canonical decimal index string constrained by `enum` to the currently permitted indices; for a List whose member declares a [key](../reference/schema.md#enumsource-keys), the member's key as a string (decimal for an integer key) constrained to the current keys instead. Include the corresponding current choice values in schema descriptions. Resolve the returned index or key through that same choice snapshot and normal constraint rules, and store the index or the typed key. A [List host](../reference/schema.md#enumsource-list-hosts) is a JSON array of these strings; a repeated element rejects the proposal, and `Size` applies. |

- `doctrans.conversion.bitflags-mask`:

  | Libertas value | Model JSON and client conversion |
  | --- | --- |
  | Bitflags | Decimal mask string with the declared catalog described in the schema; convert exactly and reject undeclared bits. |

- `doctrans.conversion.table-preserve-omitted`:

  | Libertas value | Model JSON and client conversion |
  | --- | --- |
  | Table | JSON object with every selected child required and no extra properties; recursively convert children while preserving omitted values in an existing Table or unchanged Union variant. |

- `doctrans.conversion.union-marked-payload`:

  | Libertas value | Model JSON and client conversion |
  | --- | --- |
  | Union with a marked payload field | An `anyOf` of variant objects, each with required `variant` and `value` properties and no extras. `variant` is that branch's canonical name; `value` holds that variant's marked payload fields (an empty object when it has none). Convert through the ordinary Union discriminator and payload codec. |

- `doctrans.conversion.physical-unit-semantics`: Physical units retain their ordinary semantics; they do not change the underlying value type.
- `doctrans.schema.hidden-fixed-excluded`: Hidden and Fixed values never enter the model's output schema or its returned result; normal initialization and validation continue to govern them.
- `doctrans.enum-source.snapshot`: Dynamic EnumSource is resolved from the bound Request when the request is prepared. Its current choices, allowed-choice constraints, and relevant reference values belong to the pending transaction snapshot. If they change before application, discard the result as stale. A choice cannot be silently reinterpreted against a newer list.

### Array import

Owner: [llm-document-transformation.md#array-import](../reference/llm-document-transformation.md#array-import)

- `doctrans.array.strict-forms-root`: The output schema is a JSON object with one required property, `forms`: an array whose every element is that struct's selected result object, as for a single import, with at least one element. The root stays an object because structured-output providers (OpenAI) require one. The client appends to the instruction that the model returns one element per distinct form in the files, that the files may contain duplicate copies of a form which count once, and ignores forms of other types and pages that are not forms.

### Categorized import

Owner: [llm-document-transformation.md#categorized-import](../reference/llm-document-transformation.md#categorized-import)

- `doctrans.categorized.independent-transforms`: A person often holds a pile of different documents at once, such as W-2s, several 1099s and a K-1. One transformation over every form at once is impractical: one instruction cannot carry every form's instructions well, and one result schema with every form's fields exceeds provider schema limits and degrades accuracy. A **categorized import** first sorts the files into documents of known kinds, then imports each document with its own kind's declaration.
- `doctrans.categorized.model-page-ownership`: `kind` is an enumeration with one unique name per offered kind; its `description` lists every kind with the name text described under **Kinds**, as an Enumeration's choices are described. `file` is the one-based position of a file in that request, and `pages` its one-based pages; an image has one page. The appended generic text says that the result has one element per distinct document, that duplicate copies of a document count once, and that pages belonging to no offered kind are listed in `not_imported` instead. The import itself does not open or count the files: it forwards them as selected and relies on the model's page numbers; a document whose pages do not exist fails its import and becomes Enter by hand, and so does a document whose part names a file the request does not contain or no pages; an unusable `not_imported` entry is listed as given. One bad entry never ends the import. Only the transport of a [client provider](../reference/llm-client-requests.md#provider-mapping) may convert a file for a provider that cannot take it, such as a PDF sent as one PNG per page; that conversion never decides the import's pages.
- `doctrans.files.never-rewrite-pdf`: **Import.** For each classified document, the client sends that kind's own transformation: the kind's instruction, its [array import](../reference/llm-document-transformation.md#array-import) output schema, and its `validation` facts resolved in a new item of its category List constructed by normal initialization. The request carries the files the document's pages lie in, and the instruction names those pages, for example "Read only pages 3 to 4 of file 2". Clients never split or rewrite a PDF. Classification and import requests run concurrently, with at most four of an import's transformations pending at once, the Hub's limit.

### Client workflow

Owner: [llm-document-transformation.md#client-workflow](../reference/llm-document-transformation.md#client-workflow)

- `doctrans.image.resize-with-quality-floor`: The **AI Import** action (Chinese: **AI导入**) first offers **Take a picture** using ordinary camera capture when the effective MIME filter accepts camera JPEG and a camera is available, and **Open a file**. Each action selects one file by default, or up to eight in user-selected order when enabled. Captured photos use the same preparation and validation as selected image files. Clients decode and orient selected images, flatten transparency on white, and JPEG-encode them at no more than **3,000,000 content bytes each**. Image preparation must keep reducing an oversized image's dimensions until its JPEG fits. JPEG quality stays at or above 70, and the source is never upscaled. An image that cannot be decoded or encoded reports an input error. PDF bytes remain unchanged. Clients check the combined LMB1 limit of 8 MiB before sending.
- `doctrans.import.waits-for-calculations`: Like Submit, AI Import is disabled while any calculation is pending or deferred and becomes available when the calculations settle, so no calculation result can arrive during a transformation and make its reply stale.

### Attribute encoding envelope and primitives

Owner: [descriptors.md#attribute-encoding-envelope-and-primitives](../reference/matter/descriptors.md#attribute-encoding-envelope-and-primitives)

- `matter.descriptor.format-version`: The current Matter binary-schema format version is `5`. Generators must write the format version from the loaded `BinarySchema`; `5` is the fallback when no schema is loaded. The format version is distinct from Matter device-type and cluster revisions.
- `matter.descriptor.positional`: All structures are positional. There are no field tags, field names, alignment padding, or per-field byte lengths.
- `matter.descriptor.load-type`: Studio currently exposes `LOAD` and `LOAD_NON` in both editors. Decoders retain the protocol's `LOAD_ANY` value.
- `matter.descriptor.load-meaning`: `LOAD` indicates that the device acts as a server; otherwise it is a client.
- `matter.descriptor.access.read-server`:

  | Bit | Physical-device meaning | Virtual-device meaning |
  | ---: | --- | --- |
  | `0x01` | Read | Server |

- `matter.descriptor.access.write-client`:

  | Bit | Physical-device meaning | Virtual-device meaning |
  | ---: | --- | --- |
  | `0x02` | Write | Client |

- `matter.descriptor.access.values`: The value may be `1`, `2`, or `3` when both capabilities are required.
- `matter.descriptor.numeric-ids`: Matter IDs are serialized numerically, not by localized name. Studio reserves `0xffff` as the `Any` device-type and cluster ID in selectors.

### `DeviceType` attribute for `LibertasDevice`

Owner: [descriptors.md#devicetype-attribute-for-libertasdevice](../reference/matter/descriptors.md#devicetype-attribute-for-libertasdevice)

- `matter.descriptor.physical-match`: Within one match, device-type IDs are logically ORed and cluster requirements are logically ANDed. Top-level matches are logically ORed. Consequently, a physical device matches when it satisfies at least one complete match entry.

### `VirtualDevice` attribute for `LibertasVirtualDevice`

Owner: [descriptors.md#virtualdevice-attribute-for-libertasvirtualdevice](../reference/matter/descriptors.md#virtualdevice-attribute-for-libertasvirtualdevice)

- `matter.descriptor.virtual-widths`: `revision` and `clusterRevision` are semantically unsigned 16-bit values even though the binary serializer uses `uvarint32`. `featureMap` and all Matter IDs are unsigned 32-bit values. Fixed attribute values are signed 64-bit values.
- `matter.descriptor.virtual-match`: Device types are logically ORed; clusters are logically ANDed. The cluster lists explicitly describe the virtual endpoint's supported attributes, events, accepted commands, and generated commands. The feature map and fixed values participate in Matter conformance evaluation performed by Studio's editor.

### Canonical generation and decoding

Owner: [descriptors.md#canonical-generation-and-decoding](../reference/matter/descriptors.md#canonical-generation-and-decoding)

- `matter.descriptor.decode`: Decoding performs the inverse operation. A current decoder reads the version first and then decodes the descriptor matching the schema attribute. Canonical generators append no trailing bytes. The descriptor type is determined by the attribute name; it is not tagged inside the binary payload.

### Libertas Message Format

Owner: [message-format.md#libertas-message-format](../reference/message-format.md#libertas-message-format)

- `lmf.renderer.libertas-aware`: Libertas Message Format (LMF) is the platform standard for localized, structured messages. LMF version 1 (LMF1) is based on ICU MessageFormat 1 (MF1) and adds Libertas semantic types. An LMF1 renderer must therefore be a Libertas-aware implementation; substituting a platform ICU formatter is not sufficient.

### Uses and storage

Owner: [message-format.md#uses-and-storage](../reference/message-format.md#uses-and-storage)

- `lmf.formatted-text.wire-unchanged`: and does not change its wire serialization.

### Libertas Message Arguments

Owner: [message-format.md#libertas-message-arguments](../reference/message-format.md#libertas-message-arguments)

- `lmf.renderer.no-template-reparse`: `FormattedText` is distinct from `ResourceText`: its Avro Bytes payload contains the nested resource key **and its own typed arguments**. The client renders that tuple with the enclosing message's package catalog, locale, and permission-aware object resolver, then inserts the result as text. It never reparses the rendered text as a template. The encoded argument type wins over a conflicting template type, so a nested value is not treated as a number or selector. Malformed nested tuples fail the containing render.

### Temporal arguments

Owner: [message-format.md#temporal-arguments](../reference/message-format.md#temporal-arguments)

- `lmf.timeonly.end-of-day`: Render `DateOnly` as a localized Gregorian date without a timezone conversion. Render `TimeOnly` as a localized clock value without a timezone conversion or a timezone name. Its upper-bound value 86400 means end of day: display localized `24:00` for `short`, and `24:00:00` for the other styles; do not wrap it to midnight. Render `DateTime` in the consumer's rendering timezone, using Gregorian calendar fields. A client normally uses the viewer's local timezone; a renderer without an explicit timezone uses its runtime's local timezone.

### Runtime fields with `FormattedText=true`

Owner: [message-format.md#runtime-fields-with-formattedtexttrue](../reference/message-format.md#runtime-fields-with-formattedtexttrue)

- `lmf.formatted.resolve-at-display`: The client decodes both Bytes tuples before looking up `READY` or `ROOM_STATUS`. It resolves arguments at display time and never parses a rendered sentence back into data.

### Charting

Owner: [message-format.md#charting](../reference/message-format.md#charting)

- `lmf.chart.no-diagnostic-text`: An unresolved or malformed optional formatted chart value is omitted. If the value is essential to distinguish visible categories or supply an accessible name, the complete chart follows the [chart failure policy](../reference/charts/2d.md#10-validation-and-fallback). Raw keys, blobs, and typed diagnostics may appear in bounded developer diagnostics, never as chart presentation text.

### Failure handling and review checklist

Owner: [message-format.md#failure-handling-and-review-checklist](../reference/message-format.md#failure-handling-and-review-checklist)

- `lmf.failure.preserve-canonical-value`: Rendering must never mutate canonical data. If a template, resource, object, Enumeration, unit, argument, or style cannot be resolved, retain the encoded value and use the established safe fallback outside chart presentation.

### Declaration

Owner: [network-access.md#declaration](../reference/network-access.md#declaration)

- `net.approval.raw-host-key`: Control clients present `AccessHost` entries in the same explicit task-access approval view as function `Permissions`. For every host entry, the UI always shows the raw key verbatim—for example, `api.weather.example`—and separately shows its localized `StringResources` value as the reason. Localized text never replaces or hides the host identity because the user must be able to verify the actual network name being approved.

### AI tool-call descriptions

Owner: [presentation.md#ai-tool-call-descriptions](../reference/presentation.md#ai-tool-call-descriptions)

- `ai.hints.value-representation-only`: These hints describe the tool JSON representation. They do not change App storage or protocol encoding, supply missing values or dynamic choices, or execute copying or calculations.

### Display Template

Owner: [presentation.md#display-template](../reference/presentation.md#display-template)

- `tpl.forward-tolerant`: **Forward tolerant.** A client that meets a token value, layout, hint, or member it does not know uses the platform default for that item and applies the rest of the rule, so a newer template never breaks an older client.

### Display Template catalog

Owner: [presentation.md#display-template-catalog](../reference/presentation.md#display-template-catalog)

- `layout.row.label-value-fit` (judgment): In ordinary label-and-value rows, every client measures the rendered label and value, including icons and controls, using the current font and localized text. It keeps both beside one another when their natural widths and the platform's label-value gap fit the available row width; otherwise the value goes below the label. An editable row's trailing link button counts as part of the value; when value and button do not fit beneath the label either, the button moves below the value at its trailing edge. The available width belongs to the actual pane, region, or `form-grid` cell. This client-owned arrangement needs no additional template rule.
- `layout.summary.item-editing`: **Commit** validates the complete item through the ordinary validation owners, including dynamic rules when their context is available, and waits for available pending calculations. Errors keep the item editor open. Successful Commit returns to the summary, preserving committed fields and the list position, without implicitly submitting the parent Request or making a backend workflow transition. Back from an existing item preserves its ordinary committed-field draft behavior. The client tracks the original item through list changes by stable identity, and nested summary Lists use the same navigation. An item with validation errors shows an error indicator on its preview even when the offending field is outside `paths`. Add initializes a provisional item through the ordinary List lifecycle and opens its complete editor. The normal automatic editing flow still applies: an unanswered promoted Union opens its variant chooser, while defaults and automatically resolved single choices retain their ordinary behavior. Opening an existing unanswered item uses the same automatic chooser; cancelling it retains the existing item. Opening an answered or defaulted item does not ask again. Choosing a variant does not commit the item. Cancelling this initial required chooser, or Cancel including local Back from the new-item editor, removes that exact provisional item; only a successful Commit retains it and returns to the summary. The schema's membership, add, delete, and reorder rules still apply. Editable Lists expose a visible Delete button for each complete item, including compact items; the button removes the item through ordinary List mutation and never opens its editor. ReadOnly List membership suppresses Add and Delete without recursively making members read-only. While any new item is provisional, the client pauses ordinary durable draft saves; Commit or Cancel resumes them through the normal draft owner, so recovery never restores an uncommitted new item. The parent Request cannot be submitted while a provisional item remains. Clients that do not recognize `summary` use ordinary `rows` under the forward-tolerant rule.
- `pane.title.minimum-content-width`: **Content sizing in Studio and the web interface.** Every full-screen protocol page, including Request and Response pages, uses an automatic preferred width. Simple forms use their labels and ordinary input space; tables reuse their measured column minimums, while multi-column forms and split layouts include their column and pane requirements. The page always fits its available space, including a narrow window or a resized master-detail pane. Each pane computes its own content and control requirements. The parent combines those widths with the divider gap and fits the arrangement within the window; a changed pane page or layout recalculates the combined width. Without a saved divider position, the two panes use their content-based widths rather than allocating unused width from the `panes` ratios. A manual divider position takes precedence; the editor fills the pane the viewer allocates. Typing ordinary field values does not widen a form. Summary item editors and read-only item viewers use the same automatic content sizing on their own full screen, including the header and action row; they retain the complete parent draft and their ordinary item navigation. Short forms place their actions immediately below the content. Long forms scroll the data while keeping the action row visible. Actions align with the form's left edge and stay together: eligible Go back controls followed by primary Next, or Close followed by Next in an embedded editor. The existing [pane divider](../reference/presentation.md#master-detail-panes) adjusts the master-detail allocation. The top bar shares the content layout's width, with its buttons right-aligned to the content inside the page's padding. The chosen arrangement of its title and buttons supplies a minimum content width, capped by the available window or pane space; controls follow the ordinary wrapping and placement rules when that minimum cannot fit. A master-detail working surface retains its shared top bar across the whole arrangement.
- `pane.editor.requires-correlated-leading-response`: **Initial leading-page load.** On installing an associated editor at or above the [pane threshold](../reference/presentation.md#pane-threshold), construct `refresh` from the current complete editing Request by the shared [construction-context rules](../reference/schema.md#context-and-syntax), without a bound member, and send it as an ordinary correlated Request. Keep the editor's server-supplied interaction reference context unchanged. The reply must supply the declared leading Response before the editor is embedded; a same-named Response already on screen is not evidence that it is the right section or record. Resolve placement before rendering the editor: during this load keep an existing leading page in its current layout, or show the ordinary loading surface when no page is present. Render the editor directly in its resolved pane, without first showing it on the whole working surface. The same order applies when navigation chooses another editor while the leading pane is already visible. This supports entry into any section, including direct entry with no previous leading page. It never submits the editing Request automatically. On failure, report the ordinary error and keep the editor usable as a full page. A stale or duplicate reply cannot replace another editor.
- `master-detail.refresh.commit-only`: Validation failures, rejected commits, Close, calculations, and a link return that keeps the editor open do not refresh. Never replay the Request that originally produced the leading page: it may have been a save.
- `pane.collapse.preserves-editor-navigation`: **Divider and collapse.** Two panes show one platform-owned divider in the gutter. Dragging it moves the leading pane's edge between the leading minimum and the width that leaves the trailing minimum; releasing more than half the leading minimum below it **collapses** the leading pane, and releasing between those points snaps to the leading minimum. A double click or double tap restores automatic content sizing in Studio and the web interface, and the default share on Android and iOS. The divider is focusable between the panes and is a splitter for assistive technology, valued in percent and named for the leading page ("Resize Income pane"): Left and Right (mirrored for right-to-left) move 5% of the width shared by the panes, Home and End go to the two limits, Enter toggles collapse, and Android and iOS add Collapse and Expand screen-reader actions. A collapsed leading pane leaves one button, titled with the leading page's title and a sidebar glyph, at the leading edge of the trailing pane's heading; it restores the leading pane, which keeps its scroll position and loaded value. Collapse is still the embedded arrangement: Close and Next stay, Go back stays suppressed, and collapse and expand send nothing and never refresh. Only the leading pane collapses. With no editor in the trailing pane the leading page shows expanded. Collapse moves focus to the expand button when focus was in the leading pane, and expand moves it to the leading pane's first control; both announce the new state.
- `master-detail.viewer-preference`: **Viewer preference.** The divider position and collapse state are a viewing preference kept on the viewer's device for that Hub, package, and leading Response, separately for a `medium` window and for an `expanded` or wider one. They are never App data, never sent to the Hub, never part of a draft, and never written to `display.json` or the compiled template. The stored share is applied within the current limits and kept as stored, so narrowing and widening again restores it; an unreadable value is ignored. A stacked arrangement ignores the preference without resetting it.
- `layout.columns.ratios-never-wrap`: Adjacent split regions have a platform-owned gutter in either direction and at every nesting level: 16 dp on Android, 16 pt on iOS, and 16 CSS px in Studio. Stacked lines of a `row` split are separated by the same gutter. The gutter is automatic, requires no `display.json` member, and is independent of the row `spacing` style token. A `column` split divides its height by the ratios after the gutters are deducted and never wraps. Nested device conditions use the resulting region size.
- `layout.tabs.scroll-full-labels`: `tabs` shows every tab label in full. When the labels do not fit across the pane, the tab strip scrolls horizontally; labels are never truncated and tabs never fall back to stacked sections.

### Width-based layout

Owner: [presentation.md#width-based-layout](../reference/presentation.md#width-based-layout)

- `layout.width.text-scaling`: dp is the Android density-independent pixel, an iOS point, and a CSS pixel in Studio. A `min_width` scales with the platform's text-size setting, by its factor clamped between 1 and 2, so larger text reflows sooner. A region that holds a nested split needs at least the largest minimum among the nested regions, because the nested split can itself stack.
- `layout.width.window-cap`: The window's width class caps the structure. The window is the app window on Android and iOS, Studio's view, or the simulated device of a preview; a pane never raises the cap:

  | Window width | Panes at most | Split line or `form-grid` columns at most |
  | --- | --- | --- |
  | `compact` | 1 | 1 |
  | `medium` | 2 | 2 |
  | `expanded` | 2 | 3 |
  | `large`, `extra-large` | 2 | 4 |

- `layout.width.compact-height-one-pane`: A window whose `height` class is `compact` shows one pane, so a phone in landscape keeps room for the editor above the keyboard; its columns keep the width cap.

### Split lines

Owner: [presentation.md#split-lines](../reference/presentation.md#split-lines)

- `layout.wrap.preserve-field-order`: **Split lines.** A `row` split places its regions greedily in declaration order. A region joins the current line while the line holds fewer regions than the column cap and the line's minimums plus one gutter per adjacent pair fit the split's width; otherwise it starts the next line. A region alone on a line takes the full width, and its content wraps; nothing scrolls horizontally. Within a line the width after gutters is divided by `ratio`; a region that would fall below its minimum gets its minimum, and the rest is divided again by ratio among the others until no region is below its minimum. Lines stack in order, and reading, focus, and field order stay region order then field order; wrapping never reorders. Each nested split uses its region's resulting width and the same window cap. Right-to-left text mirrors each line.

### Table column fit

Owner: [presentation.md#table-column-fit](../reference/presentation.md#table-column-fit)

- `layout.table.column-fit`: **Table column fit.** A `table`, or a `summary` with `arrangement: "table"`, shows as a table when its column minimums plus a gutter between each pair fit its width; otherwise it shows the rows or list it falls back to. The fit measures text in the current font, text size, and locale, never a size-class token. A column's minimum is the largest of its header's longest word and each current cell's minimum: a number, money, date, unit, enumeration, or Boolean cell counts its full width, and a wrapping text cell counts its full width up to the larger of its longest word and twelve times its font size. Width left over goes to the columns holding wrapping text, in proportion to their full widths, or, when no column wraps, to every column in proportion to its minimum. Nothing scrolls horizontally. The choice depends on the displayed data and is the same for the same value; when a data change switches between table and rows, the reading position stays on the same member.

### Named action controls

Owner: [presentation.md#named-action-controls](../reference/presentation.md#named-action-controls)

- `actions.controls.normal-touch-targets`: Actions is the prominent page-heading control. Its default treatment uses a filled accent background, contrasting text and a semibold caption. Optional named controls use secondary emphasis: a transparent background, subtle outline and normal-weight caption. Controls share normal platform sizing, padding and corner treatment; emphasis never reduces their touch targets. Authored emphasis and density tokens retain their ordinary overrides.
- `control.group-placement` (judgment): When at least one optional named control is visible and eligible, keep all visible page controls, including Actions when eligible, together as one group. Named controls use field declaration order; Actions is always last, at the logical trailing end (right in a left-to-right layout). Put the group in the top bar only when the full title and complete group fit after native navigation and system controls reserve their space. Never promote a smaller subset or shorten a full title to admit buttons. With no eligible Actions, measure the named controls alone by the same rule.
- `actions.controls.secondary-row`: When the group cannot fit, put every button on a new secondary row below the heading and above content, leading aligned with full captions. Neither Actions nor a custom button then shares the content title row. Show the full title in the top bar when it fits there alone; otherwise retain the full, wrapping content heading. Suppress a repeated content heading, but retain its other controls and values. The secondary row wraps as space requires, preserving order; an individually oversized caption wraps inside its button and grows its height. Actions retains its prominent styling.

### Add time values

Owner: [presentation.md#add-time-values](../reference/presentation.md#add-time-values)

- `field.ask-on-add.meaning`: **Add-time values.** `AskOnAdd` marks a value the person gives before a List member exists: a field of a List's member Table, reached from the member through struct fields, never through another List; or a List field whose member has a value of its own, which asks the member itself. A field's marker applies wherever its Table is such a member. Any number of a member's fields may carry it, of any type that has a value of its own: a scalar (String, Number, Boolean, DateOnly, DateTime, Bytes), an Enumeration, Bitflags, or a Union. When the person chooses **Add** on the List, the client first asks for every marked value; confirming creates the member with those values and every other field at its normal initialization, then shows the new member as the List's layout shows any new member: a [summary](../reference/presentation.md#summary-array-items) opens its item page, and inline rows, a table, or an accordion show it in place. Without every value, no member is added, and Cancel adds nothing.

### Common field metadata

Owner: [presentation.md#common-field-metadata](../reference/presentation.md#common-field-metadata)

- `field.ask-on-add.generated-ui`: **The platform builds the Add step.** The App declares nothing beyond the markers. One marked choice (a Union, Enumeration, or Bitflags) is asked in the [one-field popup](../reference/protocols.md#single-action-confirmation-and-input-presentation) form of its chooser; anything else, one scalar or several values, is one compact Add editor holding exactly the marked nodes, in the member's declaration order, each drawn, labeled, and validated as in the member's own editor, with its allowed choices, `RangeMin`/`RangeMax`, `Pattern`, validation rules, and calculations, and the member's display template applied. A marked Union asks its variant only; its payload is edited after Add.
- `field.ask-on-add.value`: **A value is a real answer:** a Union variant, an Enumeration member, at least one Bitflags flag, or a scalar that is neither null nor an empty String or Bytes and that passes the field's own validation. A Nullable's null answer is not a value here.
- `field.ask-on-add.person-only`: **The person's Add only:** a member that AI Import, CopyTo, an EditRequest, or the App adds is unaffected, and after Add each value is an ordinary field; whether the person may change it is a template [`lock`](../reference/presentation.md#presentation-locks)'s, a separate concept.
- `fields.compact-choice.preserve-semantics`: The [hierarchical single-field request builder](../reference/protocols.md#hierarchical-single-field-requests) also presents a choice-only Union/Enumeration hierarchy as expandable groups. It derives grouping from the App's types and removes groups with no eligible leaves recursively. This is the default compact request presentation for that shape; ordinary content folding and the Display Template do not change the underlying selection type, constraints or submission behavior.
- `template.runtime.row-replacement-stale-actions`: Cancel preserves the previous value. A source-row replacement, reorder, removal, label change, or change of a member's key value while the picker is open rejects that pending selection; reopen the picker to choose from the current list.

### MIME-aware Bytes presentation

Owner: [presentation.md#mime-aware-bytes-presentation](../reference/presentation.md#mime-aware-bytes-presentation)

- `media.control`: A `Bytes` field with effective [`MimeTypes`](../reference/schema.md#mime-aware-bytes) uses one media control in Android, iOS, Studio, and the pure Web client. Its compact row shows the field name, a bounded thumbnail for a safely decodable raster image or a generic media icon, the exact MIME and content size when defined, and no local path. Undefined media shows “No file selected.” It never displays Base64 or hex. Ordinary Bytes keep their existing editor.
- `media.invalid.generic-icon-and-error`: Editable media rows offer Choose file, Remove, and Take photo when the field accepts an image format that the client's camera can produce. The camera stores its output as a private temporary file and the editor holds a local link until outbound encoding. Camera permission denial or cancellation leaves the previous value intact; Choose file remains available. Read-only rows offer preview when supported. Tapping or clicking the thumbnail opens a larger platform preview. A corrupt or unsupported image shows the generic icon and an error when previewed; clients do not render SVG or arbitrary HTML as an inline image. Local paths and temporary link identifiers are not displayed or sent to the Hub. Camera availability is conditional on device, browser, and host permissions.

### Inline media size

Owner: [presentation.md#inline-media-size](../reference/presentation.md#inline-media-size)

- `media.inline.intrinsic-size-limit`: **Fit.** It uses the full width available to the value in its pane or `form-grid` cell, preserves its aspect ratio, and is never drawn larger than its intrinsic size, one image pixel per logical point (dp on Android, pt on iOS, CSS px in Studio). Its height is capped by the [height token](../reference/presentation.md#height-tokens): about 25%, 40%, or 60% of the nearest pane's visible height. A wide image therefore fills the width; a tall one stops at the cap and is centered. The cap follows the pane as it resizes.
- `media.presentation.preserve-business-values`: `media` is presentation only: it never changes the stored bytes, their MIME type, validation, `CopyFrom` or `CopyTo`, encoding, or AI Import.

### Protocol page heading and top bar

Owner: [presentation.md#protocol-page-heading-and-top-bar](../reference/presentation.md#protocol-page-heading-and-top-bar)

- `heading.adaptive-actions-placement` (judgment): **Adaptive heading and Actions placement.** Apply this rule on every client to a protocol page with its own screen top bar, at any screen width. It is independent of the pane threshold for two-panel layouts. **Actions** is the control for the displayed variant's eligible `NextRequest` choices, under the [request-selection rules](../reference/endpoint-presentation.md#protocol-transaction-stack); it is separate from an editor's primary Next/Submit and previous-action controls. Render its full localized caption, resolved by the [Actions caption precedence](../reference/presentation.md#named-action-controls) (Actions by default, or the active page's [button label](../reference/presentation.md#button-labels)), and retain the platform's normal button target and spacing. The fit rules below measure that resolved caption. They place Actions while it keeps this default heading placement; an `action_controls` placement of `secondary` moves it into the [named-control region](../reference/presentation.md#named-action-controls) instead.
- `heading.fit.preserve-font-and-targets`: Measure the title and button with their actual localized text, current font, weight, and accessibility text size. **Fits** means the complete text renders without clipping or ellipsis in the normal top-bar layout; never reduce the font size or button target to make it fit. Available title width is the actual title-text slot after reserving system insets, navigation and other controls, icons, gaps, and padding, including any space required by native title alignment. A title icon does not count as title-text space.
- `heading.actions.preserve-title-reservation`:

  | Available space | Top bar | First content row |
  | --- | --- | --- |
  | Actions cannot fit while preserving that title reservation | Ordinary compact title, with ellipsis as needed | Retain the full, wrapping title and Actions at its trailing end. |

- `heading.ellipsis.retain-content-heading`: An ellipsized title never qualifies for suppressing the content heading. With no eligible action, omit Actions and apply the full-title fit test alone. Place the heading's Actions control in one of these two locations, never both. If Actions cannot fit beside the content title, wrap it beneath the title within the heading area, before the separate named-control region. Keep its full caption and prominent treatment in either location. This independent Actions rule applies only when no optional [named control](../reference/presentation.md#named-action-controls) is visible and eligible. With one or more, place the complete button group under the named-control rule: full title and all buttons in the top bar when they fit, otherwise all buttons on a new row below the heading. Suppressing the heading removes only its redundant title presentation: retain any Union selector, promoted value, other row control, validation message, and requested inline description in the content, with their ordinary behavior. Content retains the root alignment and normal page padding above even when its heading moves to the top bar.
- `heading.description.accessible-action`: **Description and accessibility.** A top-bar title supports the same long press as a regular title row and opens the same localized description popup. Keep a keyboard-accessible and screen-reader-accessible **Show description** action when a description is available; help must not depend on hover or long press alone. Moving the heading preserves its full accessible name and heading semantics. If the content heading remains, expose that as the page heading without adding a duplicate heading for the compact top-bar title. Description lookup remains bound to the same active page when its title is shortened or relocated.
- `heading.width.measure-native-slots`: In a [master-detail arrangement](../reference/presentation.md#master-detail-panes), only the leading page uses the working surface's top bar. The trailing page retains its own content heading and eligible Actions; selecting it does not take over the shared title or toolbar. A page shown on the whole working surface after narrowing uses the same placement rule for its own title and Actions. Re-evaluate the complete candidate set when the available width or height, title, control availability or captions, template, locale, font, text size or device conditions change, including rotation and window resizing. Restore controls to the top bar when they fit again. Measure actual localized controls and native slots, never a device-category breakpoint or a fixed estimate for system controls. Preserve logical order in right-to-left layouts and the full accessible names. Re-layout preserves values, focus, interaction and reference-context ownership, and pending-operation restrictions, and sends no request.

### Money

Owner: [presentation.md#money](../reference/presentation.md#money)

- `money.readonly.visible-value`: Android and iOS edit money in place. The editor shows the currency code beside an ungrouped amount using locale digits and its decimal separator. Committing `123.45` for USD/2 stores `12345`. Grouping separators, currency symbols inside the amount, excess fractional digits, and values outside signed i64 are rejected without replacing the entered text. `ReadOnly` amounts remain visible and cannot be edited. Nullability follows the ordinary schema contract.

### Expanding long displayed text

Owner: [presentation.md#expanding-long-displayed-text](../reference/presentation.md#expanding-long-displayed-text)

- `text.expansion.preserve-size`: Clients that shorten long displayed text behind **Show more** measure the full text using its actual font, styling and available width. If it fits within the combined height of the shortened preview, disclosure row and their spacing, display the full text and omit the disclosure. Do not reduce the text size to make it fit.

### AI tool-call output

Owner: [presentation.md#ai-tool-call-output](../reference/presentation.md#ai-tool-call-output)

- `ai.output.unresolved-formatted-error`: The Hub renders each `FormattedText` tool result as a localized JSON string using the emitting package's resources and locale. Templates and the resource catalog remain internal to rendering; they are not included in the AI tool description or result. If a value cannot be resolved or rendered, its result is `{"error":"Unresolved FormattedText"}` rather than guessed text.
- `ai.output.preserve-readonly-bytes`: This output view does not change the canonical LMF1 tuple. Read-only request fields retain their original Base64 Bytes representation in tool arguments; rendered text cannot replace that payload for copying or resubmission. Ordinary Bytes remain unchanged.

### Inline choice ladder

Owner: [presentation.md#inline-choice-ladder](../reference/presentation.md#inline-choice-ladder)

- `choices.inline.no-line-wrap`: **One per line.** Each choice gets its own line beneath the title, and a long caption wraps within its line. Choices never wrap onto a second horizontal line.

### Link rendering

Owner: [presentation.md#link-rendering](../reference/presentation.md#link-rendering)

- `links.transition.single-owner`: A primitive with no label of its own, such as an item of a List of scalars, shows its value as the link text. A link retains transition ownership while its composer is open, so repeated taps never open or send twice. Editing the linked request shows no waiting spinner. Once it is submitted, the owning panel shows shared blocking progress until the reply or terminal failure, under the [waiting presentation contract](../reference/protocols.md#waiting-presentation); rows do not show a separate link spinner. A link adds no chevron or other glyph and never edits its field.

### Folding structs and unions

Owner: [presentation.md#folding-structs-and-unions](../reference/presentation.md#folding-structs-and-unions)

- `fold.disclosure-control`: An unmarked struct, union, or list shows no disclosure control and cannot be collapsed. A marked value shows one disclosure chevron at the leading edge of its title line, pointing toward the text when collapsed and down when expanded (mirrored for right-to-left text), and exposes its expanded state to assistive technology. The title line toggles the fold, except on a row that is also a [link](../reference/protocols.md#links), where only the chevron toggles and the rest of the row follows the link. A marked value starts expanded, or collapsed when `initially_open` is false, and permits manual collapse and expansion; a collapsed value shows only its row, its title and promoted value included. The initial state applies to rows that exist when a page is displayed.

### Unanswered fields and null

Owner: [presentation.md#unanswered-fields-and-null](../reference/presentation.md#unanswered-fields-and-null)

- `fields.unanswered.semantic-placeholders`: Required fields without an encodable value use subdued italic placeholders: “Select…” for choices, “Enter…” for typed input, “Choose date…” or “Choose time…” for temporal input, and “Set up…” for a non-nullable container needing initialization. Readonly fields without a value show “Not set.” Boolean choices retain their unselected Yes/No controls. These labels are localized, with English fallbacks.
- `nullable.presentation.explicit-answer-choice`: A displayed nullable null is shown inside an outlined capsule using the field document's `NullText`, or localized “None” when omitted. An editable Nullable is the [Nullable choice](../reference/presentation.md#nullable-choices) below, never a capsule with unlabelled set and clear controls.

### Nullable choices

Owner: [presentation.md#nullable-choices](../reference/presentation.md#nullable-choices)

- `nullable.blank-value.unanswered`: The null answer always comes first. While unanswered, no choice is selected and a picker shows “Select…”. A value answer chosen with its value still blank stays unanswered; null is never inferred from a blank editor. Read-only display keeps the caption described above.

### Revealed rows

Owner: [presentation.md#revealed-rows](../reference/presentation.md#revealed-rows)

- `editor.reveal.only-local-control-scroll`: Scroll only for an edit the person made on that control: a tap, click, or keypress. A calculation reply, a server-supplied value, a restored draft, a rule, or another row's change never scrolls the editor.
- `editor.reveal.keep-control-visible`: Scroll the least distance that shows the new rows, bringing as many of them into view as fit while the changed control stays visible. The changed control never leaves the visible area, and the editor never scrolls up for revealed rows.
- `editor.reveal.preserve-focus`: Follow the platform's reduced-motion setting: move without animation when it is on. Focus stays on the changed control; scrolling never moves it.
- `editor.hide.no-scroll`: Rows that disappear, such as a Table's fields after choosing a Nullable's null answer, never trigger scrolling.

### Protocol roles

Owner: [protocols.md#protocol-roles](../reference/protocols.md#protocol-roles)

- `error-field-path.hidden-target-ancestor` (judgment): A Hidden or ExcludeUI target marks its nearest visible ancestor row instead. An unresolvable path, or a closed editor, leaves only the dialog. The locator never navigates elsewhere, submits a request, or changes a value.
- `protocol.ack.transient-result` (judgment): The absence of a next state identifies an ordinary completed result without a separate attribute marking a result variant. Present its user-facing result message transiently while retaining the existing interactive state; do not replace that state with a data screen offering no further actions. This state-preservation rule does not suppress the result message. It leaves the separate initial-entry, workflow-completion, chart-presentation, and live subscription behavior unchanged.

### Field calculation requests

Owner: [protocols.md#field-calculation-requests](../reference/protocols.md#field-calculation-requests)

- `calc.inputs.only-request-complete` (judgment): Only the constructed calculation request must be complete and valid to send; the parent editor may still contain undefined, unencodable fields.

### Typed ancestor paths and per-item calculations

Owner: [protocols.md#typed-ancestor-paths-and-per-item-calculations](../reference/protocols.md#typed-ancestor-paths-and-per-item-calculations)

- `calc.instance.retained-binding`: Clients retain the bound root and ancestor identities while waiting. Resolve output paths against the original binding of the root, never the currently selected row. Removing or replacing the root occurrence, or one of its ancestors, invalidates its pending work before a reply can write. A positional mapping which would now address another item is also invalid; never retarget it. Surviving item-local bindings follow the [array-removal rule](../reference/protocols.md#array-removal-and-calculation-scope), rather than treating an index shift alone as changed item data. Rediscover affected current instances and defer/reschedule still-needed work using the ordinary calculation rules. A root occurrence that appears while the editor is open acquires its own instance under the [appearance rule](../reference/protocols.md#changes-resets-and-deferred-calculation). Reset only the destinations belonging to affected instances. This adds no wire opcode or persistent row identity requirement.

### Union calculation roots

Owner: [protocols.md#union-calculation-roots](../reference/protocols.md#union-calculation-roots)

- `calc.union.branch-change`: A branch change is an input change to the same instance, so a pending reply whose retained destinations were in the deselected branch is [obsolete](../reference/protocols.md#changes-resets-and-deferred-calculation).

### Array removal and calculation scope

Owner: [protocols.md#array-removal-and-calculation-scope](../reference/protocols.md#array-removal-and-calculation-scope)

- `calc.removal.item-local` (judgment): For removal, "outside an item" means outside that item's data subtree, not later in array order. A calculation is item-local only when its root occurrence lies in that item and all fields actually copied as inputs lie in that same item; its CopyTo destinations always lie in the root's subtree. Include nested fields and the actual projected fields of reduced copies when determining this scope; the spelling of a path alone does not establish locality.
- `calc.removal.cancel-removed` (judgment): Cancel that item's pending and deferred calculations. A reply cannot recreate the item or write into the item which now occupies its former index.
- `calc.removal.preserve-surviving` (judgment): Preserve surviving item-local results and work when their bound nodes and copied inputs remain unchanged. Do not reset or recalculate them merely because their array indices decreased. A pending typed-ancestor calculation may retain its original surviving item binding; a literal positional mapping which no longer addresses the original nodes must instead be invalidated.
- `calc.removal.recalculate-external` (judgment): Recalculate instances associated with the changed array which involve fields outside the individual item, such as a shared parent/root rate. This applies even if that external rate's value did not change. Reschedule through the ordered deferred queue's remove-then-append rule; outputs keep their values until a result is copied.
- `calc.removal.recalculate-whole-array` (judgment): Recalculate calculations consuming the array as a whole or a reduced array projection. Membership is an input change, including removal of the last item, even when no surviving element's individual field value changed.

### Applying calculation results

Owner: [protocols.md#applying-calculation-results](../reference/protocols.md#applying-calculation-results)

- `calc.copyto.no-navigation` (judgment): Applying a calculation result does not navigate or replace any object in the editor's reference context. The [reduced SubscriptionData design pattern](../reference/protocols.md#reduced-subscriptiondata-design-pattern) uses variant-level CopyTo lists in a stable subscription context.
- `calc.apply.atomic` (judgment): The client checks the complete mapping before applying it atomically. A null result assigns null to a nullable destination. An unavailable union branch, null parent, or out-of-bounds array destination rejects the complete update; CopyTo never creates missing parents or extends arrays. A rejected update is a calculation failure: it is reported, it is not sent again automatically, and Submit stays disabled until a retry settles under the [failure rule](../reference/protocols.md#changes-resets-and-deferred-calculation). No unrelated field, default initialization, or navigation is changed by a successful assignment.
- `calc.apply.only-successful-copyto` (judgment): **A calculation changes the editor only through the CopyTo of a successful result.** An Error response, timeout, transport failure, rejected update or obsolete reply copies nothing and leaves every output as it was.
- `calc.apply.ordinary-change`: An assigned value is an ordinary editor change: the editor re-lays out exactly as for the same edit by the user, under the [presentation rules](../reference/presentation.md#display-template), with no calculation-specific layout handling.
- `calc.apply.link-return` (judgment): A [link return](../reference/protocols.md#link-return) applies these rules to a plain reply to an ordinary link sent from an editor; it may write a calculation's destinations, and the later assignment wins.

### Changes, resets, and deferred calculation

Owner: [protocols.md#changes-resets-and-deferred-calculation](../reference/protocols.md#changes-resets-and-deferred-calculation)

- `calc.schedule.root-appears` (judgment): A [root](../reference/protocols.md#calculation-root) occurrence that appears while the editor is open, an added List member or a selected Union branch whose payload carries the root, creates its instance and schedules it, also when every input has a Default; it is sent once its inputs are complete.
- `calc.schedule.typed-changes`: Changes are measured as typed editor values, including becoming undefined; formatting alone is not a new input. Invalid intermediate input still invalidates pending work.
- `calc.schedule.buffered-edits`: Native inline editors may buffer text until Done. Buffered changes invalidate pending replies immediately and defer sending until the local edit finishes. Submit requires that edit to finish; an independently valid Prev request, when offered in the current presentation, remains available to discard it.
- `calc.submit.disabled-while-pending` (judgment): While any calculation is pending or deferred, Submit is temporarily disabled; nothing is queued to send later. An output always shows its value, never placeholder text; only the [`:pending`](../reference/presentation.md#display-template) style state of the node that owns the operation marks the wait. The fields stay editable, and an edit starts a new calculation round that keeps Submit disabled. Submit becomes active again when the calculations settle, and then validates and sends its snapshot; a [RequestConfirmation](../reference/protocols.md#request-confirmation) dialog comes after that validation, and its Send sends the validated snapshot. A calculation failure shows its error and keeps the editor open.
- `calc.editor.links-during-pending` (judgment): Links, an offered Prev action, and exit stay available during calculations: leaving the editor discards the round and its unfinished values. A link chosen while a calculation is pending constructs its request from the values as displayed, including a calculated value whose newer result has not arrived. The [pane contract](../reference/presentation.md#master-detail-panes) still suppresses Prev in an embedded trailing pane.
- `calc.blocked.map-to-owner`: When a required output is still undefined because its calculation has never run, an input being incomplete or invalid, reconstruct and validate the calculation request from its current inputs. Preserve the concrete failure's explanation and map its field or rule-owning group through the input [projection](../reference/schema.md#shared-copyfrom-and-copyto-rules) to the exact original current-request occurrence, including List positions, field names and the selected Union branch. Present that owner under the [validation result contract](../reference/validation-rules.md#18-validation-result). For example, a List inside an income document that violates `SizeMin` shows the size error on that List, even when the calculation copies the whole income-documents collection.
- `calc.blocked.fallback-marker`: Only when there is no concrete current-request owner to map, such as an unavailable copy, a reference-context input, an input without a matching source field, or a function-wide rule, report the blocked calculation on the trigger that last scheduled the instance, or on the root occurrence when its appearance scheduled it, and name the input it needs. Do not infer field blame from a rule expression. After an accepted edit, recheck the marked input in a freshly constructed calculation request and clear its marker when that input passes or its original occurrence is removed or replaced; do not wait for the output or transfer the marker to another List row or Union branch. A fallback marker for an undefined required output remains until that output is usable. An output that holds any value, whether its initial default, an EditRequest value or an earlier result, is never marked because of its calculation.
- `calc.outputs.keep-until-copyto` (judgment): Changing an input does not reset any output. An output keeps its last value until the next successful CopyTo. A dependent calculation runs again only when a CopyTo actually changes one of its inputs; a failed upstream calculation therefore schedules nothing. Result assignments are atomic editor changes.
- `calc.queue.one-in-flight` (judgment): Clients permit continued editing. **An editor has at most one calculation request in flight**, whatever its calculation and whatever its instance: two calculation requests are never in flight together, even when they are of different types or belong to different List items. Every other scheduled calculation is deferred. A change that schedules the root occurrence of the request in flight makes that request **obsolete**: its reply is discarded in its turn and assigns nothing. Obsolete work is never queued again on its own account; the change that made it obsolete has scheduled that occurrence by the ordinary rule below, in the ordinary order. A change that schedules nothing for that occurrence leaves the request valid and its reply is applied. Array removal cancels or preserves item-local work under the [scope rule](../reference/protocols.md#array-removal-and-calculation-scope).
- `calc.queue.ordered-entries` (judgment): Deferred calculations form one ordered queue for the editor, with **at most one entry per calculation type**, a type being one calculation Request variant. The queue orders the types. An entry records the root occurrences of its type that await evaluation, such as the List items whose inputs changed; array items of one type share that type's entry. Whenever a change schedules a calculation, add its root occurrence to the entry of its type, creating the entry when absent, and move that entry to the tail. Do not retain its old queue position or append a duplicate. For example, with the types queued as `[A, B, C]`, a change that schedules B produces `[A, C, B]`; one that schedules A next produces `[C, B, A]`. When one change schedules several types together, their entries move to the tail in the order in which the changed field's `CalculationRequest` lists them: a field declaring `"A,B"` leaves A ahead of B. When one atomic editor change alters several fields, such as the assignments of one reply, the fields are taken in assignment order and each field's types in its listed order.
- `calc.queue.dispatch-order`: The in-flight transaction keeps its immutable root occurrence and ancestor bindings. A new change queues a deferred execution; it cannot rewrite the transaction already sent, and it cannot be sent before that transaction's reply has arrived. Only after the in-flight transaction completes, with its result applied, its failure reported, or its obsolete reply discarded, is the queue head reevaluated and, when still valid, sent. Dispatch deferred work from the queue head in order, one at a time, never by schema declaration order or response completion speed, and never past a head that cannot be sent yet. Replies are therefore applied in the order of the queue, which is the order in which [overlapping assignments](../reference/protocols.md#applying-calculation-results) take effect. Calculation transactions also obey the [request/response ordering guarantee](../reference/protocols.md#request-and-response-ordering).
- `calc.queue.rebuild-at-dispatch`: A deferred entry represents work to reevaluate, not a previously encoded request. At dispatch, take the awaiting occurrences of the head entry in array order. Reconstruct each from the current input contexts and check validity. If it is invalid or its root occurrence no longer exists, drop that occurrence without sending; a relevant later input change can schedule valid work again. Otherwise send it, one request at a time. The entry leaves the queue when its last occurrence has been sent or dropped; an entry whose every occurrence was dropped sends nothing. Result assignments which trigger calculations use the same remove-and-append rule after dependency propagation. Scheduling never changes the bindings of an in-flight transaction.
- `calc.failure.no-auto-retry` (judgment): A failed calculation, whether a timeout, a transport failure, or a [rejected update](../reference/protocols.md#applying-calculation-results), changes no output and is not sent again automatically: the next edit or Submit runs it again. That Submit sends nothing; Submit stays disabled until the retry settles and is then pressed again. Obsolete errors do not report against a newer edit. Leaving or replacing the editor abandons its pending/deferred calculations; late replies cannot reopen it or update array positions that have changed ownership.
- `calc.submit.own-snapshot` (judgment): Submit and Prev send their own validated, immutable snapshots and own their navigation. Calculation replies cannot mutate a submitted snapshot. If the operation fails and the editor remains, reevaluate unfinished calculations. No partially filled parent request is transmitted.

### Entering and continuing a workflow

Owner: [protocols.md#entering-and-continuing-a-workflow](../reference/protocols.md#entering-and-continuing-a-workflow)

- `workflow.reply-refusal` (judgment): A reply that the client refuses before it takes effect is handled as a failure: the client reports the refusal by the error procedure, and the originating surface, its reference context, its actions, and its draft stay. The client refuses a correlated reply that cannot be decoded, whose variant the request's NextResponse does not admit, or whose variant carries only SubscriptionData; an EditRequest value that is not a valid value of its Request variant; a delivered editor beyond the user's access, as above; a [link return](../reference/protocols.md#link-return) that cannot be applied; and a reply whose [allowed-action list](../reference/protocols.md#response-action-constraints) is malformed.

### Operation ownership

Owner: [protocols.md#operation-ownership](../reference/protocols.md#operation-ownership)

- `ops.one-pending-transition` (judgment): EditRequest uses the same correlation, status envelope, transport failures, and error/retry procedures as an ordinary reply. Allow only one pending Submit, Prev, or [link](../reference/protocols.md#links) transition per editor; while one is pending, the others are blocked. Until its reply arrives, the editor accepts no edit, link, document import, or second transition, so a link return never arrives after the user edited its destination. A displayed value likewise has at most one pending operation: a second action or link is blocked until the reply. Each [stacked page](../reference/protocols.md#links) retains its own operation and subscription owners; releasing it drops later replies and updates without affecting another page. A duplicate or late reply cannot replace an editor after its operation was consumed, its editor superseded, or the interaction exited. Subscription updates and auxiliary calculation responses keep their own owners and cannot perform workflow navigation or overwrite a replacement editor.
- `waiting.surface.operation-owner`: Mobile Control uses one shared blocking progress presentation per client, with screen and panel scopes. Waiting follows the operation that owns the surface, never a count of in-flight messages.
- `waiting.screen.shared-progress`: **Screen progress** covers connection and required device/schema loading, endpoint landing preparation, and an ordinary committed protocol Request awaiting its correlated reply. Required Matter schema is settled before rendering a returned Matter device.
- `waiting.cancel.unsubmitted-only`: After commitment, the entire covered screen or panel is disabled: fields, links, Commit/Next, and draft Cancel/Close. Cancel discards an unsubmitted draft; it must not be offered as cancellation of an already submitted Request. Endpoint exit retains the [leaving rule](../reference/protocols.md#exit-while-pending).
- `waiting.reply.ignore-unrelated-traffic`: Only the owning reply or a terminal operation/connection failure settles the wait. Duplicate, stale, subscription and calculation traffic cannot clear it or navigate a replacement surface.

### Exit while pending

Owner: [protocols.md#exit-while-pending](../reference/protocols.md#exit-while-pending)

- `exit.pending-never-abandoned` (judgment): **Leaving while an operation is pending.** For [human-task interaction](../reference/protocols.md#human-task-interaction), protocol interaction is strictly ordered and proceeds request by request: every Request has its correlated reply, and only SubscriptionData arrives outside that order. A pending Request is therefore never abandoned on its own. A surface whose operation awaits its reply cannot be dismissed by itself: not a transient composer, and not an editor with a sent Submit, Prev, or link. For a person, every sent Request waits for its reply: a client offers no control that gives up one pending Request and stays in the endpoint. On a waiting surface, native Back or explicit endpoint exit leaves the endpoint; it never returns to the surface beneath, to a landing, or to another pane. Draft Cancel and panel Close are disabled under the [waiting presentation rule](../reference/protocols.md#waiting-presentation).

### Operation ownership

Owner: [protocols.md#operation-ownership](../reference/protocols.md#operation-ownership)

- `exit.leaving-and-session-loss` (judgment): Leaving an endpoint is part of normal interaction. A person may leave at any time, whether or not a reply is outstanding, and a task with several endpoints follows the same rule as a task with one. The Control session continues unless its transport breaks or times out. Every connection is a new connection: when the session is lost, the data of every endpoint in it is cleared, including surfaces, reference contexts, snapshots, and pending operations and calculations, and after reconnecting an endpoint starts again from its entry request when the person opens it again. In mobile Control, connection loss returns to the task's endpoint list when that root remains a list. If the task root automatically enters a landing, including a task with one endpoint, the client exits the task to its parent Control screen. Reconnection does not reopen that task or endpoint; a retained endpoint list may reload its inline default subscriptions. Every attempt to open a task or an endpoint from its task root first checks that task's Hub connection. If it is unavailable, the client reports an error and stays on the current screen. An endpoint protocol Error preserves the current screen and displays the error. Leaving abandons the endpoint's surfaces with their pending operations and calculations and queues the release of its explicit subscriptions; a reply that arrives afterwards is discarded, and nothing the backend already received is retracted. Entering the endpoint again repeats its [entry](../reference/protocols.md#default-request-entry-and-dynamic-actions) request, and [CacheIdentity](../reference/annotations.md#cacheidentity) governs any draft recovery.

### Compatible SubscriptionData

Owner: [protocols.md#compatible-subscriptiondata](../reference/protocols.md#compatible-subscriptiondata)

- `sub.compatible.every-live-screen` (judgment): Every incoming SubscriptionData report is offered to every live screen for its source Hub, including covered screens. Each independently decides whether it needs the report. Acceptance or rejection by one screen never stops traversal. Compatibility changes data application, never ownership: Hub, task, endpoint, schema context, subscription lifetime, and stale-message checks still apply. A current A may accept its explicitly named compatible reports even when the original SubscriptionRequest names only A.

### Applying a report

Owner: [protocols.md#applying-a-report](../reference/protocols.md#applying-a-report)

- `sub.append.apply-index-check` (judgment): For each append targeting that working variant, compare the optional index against the destination array's exact current length, before applying any changes from the report. A zero-based append position of zero therefore requires an empty destination. Perform this check even for an empty append.
- `sub.append.no-name-replacement` (judgment): A field carrying AppendTo never also participates in name-based replacement, even if its name matches a destination field. If its AppendTo names another working variant, ignore that field for this screen; do not fall back to ordinary replacement.
- `sub.append.mismatch-rejects` (judgment): A negative or mismatched index rejects the entire report, preserves the original working value, and reports a protocol error. Never pad, truncate, overwrite, adjust an index, partially apply other fields, or infer automatic recovery. Other assignment or resulting-value validation failures also reject the entire report. A full snapshot continues to replace its arrays normally.

### Per-destination resolution and typed copying

Owner: [protocols.md#per-destination-resolution-and-typed-copying](../reference/protocols.md#per-destination-resolution-and-typed-copying)

- `sub.reduced.atomic-update`: Prepare and validate the complete resulting working value before publishing any assignments. An invalid applicable path, stale binding, incompatible value, or violation of schema-expressible value constraints rejects that working value's update atomically. Unrelated fields, the stable Protocol Variant, presentation, and actions remain unchanged. The reduced Protocol Variant carries CopyTo for its whole payload; the selected destination type determines the checked field projection.

### Endpoint runtime rules

Owner: [protocols.md#endpoint-runtime-rules](../reference/protocols.md#endpoint-runtime-rules)

- `endpoint.restart.no-retained-stream`: A client resubscribes after a delivered server `PeerUp`. It must not assume a previous server process retained stream state.

### Persistence failure and the no-status API

Owner: [protocols.md#persistence-failure-and-the-no-status-api](../reference/protocols.md#persistence-failure-and-the-no-status-api)

- `data.failure.no-disable-or-auto-restart`: The Hub distinguishes data failures internally and reports the existing runtime `Error` status while preserving the persisted enabled/configured status. It must not permanently disable the task in the database or automatically restart it. Starting, restarting, and enabling use the same sufficient `Enable` primitive.

### Backend-constrained choices

Owner: [protocols.md#backend-constrained-choices](../reference/protocols.md#backend-constrained-choices)

- `choices.answered-selection`: **Answered selections.** A Union with a selected branch is answered, whether the branch came from a `Default`, a singleton allowed set, or a restored [draft](../reference/annotations.md#cacheidentity). When an editor opens, the client opens the first unanswered value; when a List member is added while the page is open, it opens the member's header field only while that field is unanswered. Neither opens the chooser of an answered Union: only a Union with no selection is a candidate.

### Calculated allowed sets

Owner: [protocols.md#calculated-allowed-sets](../reference/protocols.md#calculated-allowed-sets)

- `calc.allowed-set.refilter`: When a client applies a result that changes a source, it re-filters every chooser reading that source. A current selection inside the new set, and its payload, are untouched. A current selection outside the new set is reset to unanswered, discarding a removed Union branch's payload with it. The field's `Default` applies only when the new set contains it; a Default outside the set is not applied, and a Nullable field is reset to unanswered, never to null, because null would turn a removed answer into a complete one. The [singleton rule](../reference/protocols.md#backend-constrained-choices) then applies to the unanswered field. That reset is an input change for calculations reading the field and propagates through the dependency graph like any other result. The reset, any singleton selection, and the assignment are one atomic editor change.
- `calc.allowed-set.pending-keeps-set`: While the calculation of a source is pending, the source keeps its current set: the chooser offers that set, the selection is retained, and submission validates it against the current set as usual. Only a copied result that changes the set can reset a selection.
- `calc.allowed-set.draft-restore`: A restored [draft](../reference/annotations.md#cacheidentity) brings its calculated sources with it and the seeded calculations recompute them; a source no calculation writes keeps its freshly supplied value under the [draft restoration rule](../reference/annotations.md#cacheidentity).

### Single-action confirmation and input presentation

Owner: [protocols.md#single-action-confirmation-and-input-presentation](../reference/protocols.md#single-action-confirmation-and-input-presentation)

- `chooser.group.expansion-never-submits`: Expanding or collapsing a group never selects a branch, discards another branch's payload, or sends a request. Choosing a leaf assigns the complete typed branch path and enum value atomically. Cancel preserves the prior value. The popup still requires its ordinary confirmation, validation and submission, including RequestConfirmation when declared; one eligible leaf does not authorize sending. Check current eligibility again before accepting the selection and submitting. WorkflowRequest and EditRequest entry retain their normal editor and Next control.
- `actions.hierarchical.text-filter`: Above twelve permitted leaves, use the ordinary chooser text filter over localized names and descriptions, including ancestor group names. Reveal matching paths and retain the current permitted selection. Text filtering only changes visibility; it neither changes allowed sets nor makes a previously ineligible leaf selectable.
- `confirmation.dialog.origin-must-remain`: Opening the dialog does not start a pending request. **Cancel** sends nothing and returns to the surface, the popup, or the editor as it was, with the reference context unchanged. **Send** validates and sends once. A dialog whose originating surface has been left or replaced cannot send.
- `workflow.entry.explicit-submit`: Actions taps and input-based sending follow the same rule for static and constrained NextRequest sets; only constrained regular responses have the automatic input-opening behavior above. Existing explicit DefaultRequest entry, default subscriptions, and chart previews retain their own rules. WorkflowRequest and EditRequest continue to require their existing explicit editor submission.

### Links

Owner: [protocols.md#links](../reference/protocols.md#links)

- `links.stack.no-request-history`: Each successful reply opens one live displayed page with its own title, ordinary read-only presentation, eligible links and Actions, and reference context. The sending surface remains live beneath it with its draft, scroll, validation marks, pending calculations, reference context and subscription intact. Another stacked link pushes another such page; there is no depth limit. These pages are not a history of intermediate requests: no PrevRequest control is offered, and revealing a page never reconstructs or re-sends anything. Stacking affects client presentation only and does not change [AI tool eligibility](../reference/protocols.md#ai-tool-calls), including LLMToolCall exclusion and the absence of tools for SubscriptionRequest-only variants.
- `links.stacked.pending-blocks`: Each page owns at most one pending operation under [operation ownership](../reference/protocols.md#operation-ownership). While a stacked link is pending, the sending editor blocks Submit, Prev and other links; its auxiliary calculation replies still apply and subscriptions may refresh underneath. A page awaiting a sent operation cannot be dismissed alone; [exit while pending](../reference/protocols.md#exit-while-pending) leaves the endpoint and releases the entire stack. Replies and updates for a popped or otherwise released page are dropped and cannot navigate, refresh another page, or overwrite a replacement editor.
- `links.reply.ignore-superseded`: **Ownership.** A link is a pending transition owner beside workflow Submit and Prev under [operation ownership](../reference/protocols.md#operation-ownership). An editor has at most one pending transition; while a link is pending, Submit, Prev, and other links are blocked. Before submission, cancelling its composer discards that draft and releases the transition without a waiting spinner. After submission, its [panel progress](../reference/protocols.md#waiting-presentation) and [endpoint exit](../reference/protocols.md#exit-while-pending) follow the ordinary ownership rules. Calculation replies stay auxiliary and still apply unless the editor was replaced. A late or duplicate link reply cannot replace a superseded editor or screen.
- `links.presentation.preserve-label-value`: **Presentation.** [Link rendering](../reference/presentation.md#link-rendering) owns how a link appears: it never removes its row's label or value, and it never edits its field.

### Control endpoint landing and native Back

Owner: [protocols.md#control-endpoint-landing-and-native-back](../reference/protocols.md#control-endpoint-landing-and-native-back)

- `landing.endpoint-list`: A task with multiple endpoints opens an **endpoint list**. Default subscription data may be displayed directly under an endpoint in that list; only this inline subscription data also serves as that endpoint's landing. Do not create a duplicate subscription landing when its endpoint header is tapped.
- `control.single-endpoint.no-list-underlay`: Other landing content, including chart workspaces and workflow editors, is entered when the endpoint is tapped and displayed above the endpoint list. With exactly one endpoint, enter its landing directly, including a subscription-data landing: never create, display, or retain an endpoint-list screen underneath it.
- `control.landings.working-reply-not-entry`: A working reply cannot redefine either landing or become its entry source.

### Response action constraints

Owner: [protocols.md#response-action-constraints](../reference/protocols.md#response-action-constraints)

- `actions.response-constraint.malformed-refuses`: Missing, malformed, out-of-range, or undeclared entries refuse the whole reply or update before it takes effect: the prior surface, its reference context, its actions, and any draft stay. Clients never fall back to the unconstrained set.

### Named action controls

Owner: [protocols.md#named-action-controls](../reference/protocols.md#named-action-controls)

- `actions.control.malformed-rejects`: A missing, null, malformed, out-of-range, or out-of-NextRequest member rejects the entire reply or subscription update before installation. A client keeps the prior page, reference context, controls, and draft through the ordinary failure procedure. Every accepted update replaces all arrays together; an open chooser re-filters before selection. An already-open composer or confirmation follows the normal response-action lifetime rule. The service still authorizes and validates every selected Request.

### AI Navigation and AI Search

Owner: [protocols.md#ai-navigation-and-ai-search](../reference/protocols.md#ai-navigation-and-ai-search)

- `ai.availability.user-and-connector`: Control AI requires a signed-in user with a configured, supported and enabled language-model connector assigned to that user. Administrative admission is not required. Clients treat AI as unavailable on each new connection until the Hub reports availability for that session, and follow changes to the user's connector, configuration and login permission. Availability describes usable configuration; provider connection failures still appear as ordinary request errors.
- `ai.availability.refresh-on-reconnect`: This session requirement applies to Assistant messages, tool-catalog queries, AI Navigation and [AI Import](../reference/llm-document-transformation.md). Automatic tool-catalog loading waits while AI is unavailable. Schema-eligible controls may remain visible; activating an unavailable AI feature reports an immediate error and sends no AI request. Reconnecting requires a fresh availability report. While AI is unavailable, clients do not apply AI replies to the working page or editor. Page eligibility and operation-specific permissions remain separate requirements.
- `ai.navigation.no-layout-trigger`: Studio and the web interface hide Search when it is disabled, unavailable or ineligible on the current page. Ordinary page layout and availability updates do not construct navigation candidates or speech hints. Opening Search by an explicit tap prepares its speech hints only when Search is enabled and available; submitting a query constructs and validates the current candidates.
- `ai.recording.explicit-microphone-only`: Search opens a secondary text/dictation composer without changing the working page, panes, or scroll position. Recording starts only on explicit microphone action; stopping retains an editable transcript and never submits. Typing works without microphone permission. Search requires explicit submission. No match or a transient failure retains the query and page. An explicit Ask Assistant handoff is available only when Assistant is available in the stated scope; a failed search never executes tools automatically.
- `ai.search.context-limit`: The complete serialized search context is limited to 256 KiB of UTF-8 and 256 candidates, after the semantic projection described below. If that projection still exceeds either limit, the client reports that the search context is too large, retains the query and working page, and sends no AI request. An oversized context is distinct from an unavailable Search feature. Clients must not silently drop destinations or their distinguishing meaning to fit the limit.

### Prompt attribute and composition

Owner: [protocols.md#prompt-attribute-and-composition](../reference/protocols.md#prompt-attribute-and-composition)

- `ai.prompt.reuse-document-resolution`: This is an additive rule, not ordinary nearest-attribute replacement. Keep the two declaration locations through resolution. Reuse the normal package and locale resolution rules; do not implement a separate translation catalog.

### Candidate discovery and assembly before AI

Owner: [protocols.md#candidate-discovery-and-assembly-before-ai](../reference/protocols.md#candidate-discovery-and-assembly-before-ai)

- `ai.candidates.hierarchical-leaves`: For an offered action that qualifies for the [hierarchical single-field builder](../reference/protocols.md#hierarchical-single-field-requests), reuse that builder's current eligible leaves and construct a detached, complete request for each leaf. Apply every ancestor and leaf constraint, and retain its exact Union discriminants, Enumeration value, localized label, descriptions and group path. An empty chooser supplies no candidate. Do not enumerate raw schema choices or expand requests with other editable input. This does not expand actions from an editable workflow page.
- `ai.candidates.link-only`: A LinkOnly target is eligible through its actual declared link, never as an invented unbound action. Control-specific arrays do not restrict independent links; the [named-control contract](../reference/protocols.md#named-action-controls) continues to own that distinction. Folding and scroll position do not remove an otherwise available link; a Hidden occurrence is not a link.
- `ai.search.no-render-trigger`: The [pane ownership contract](../reference/presentation.md#master-detail-panes) still applies. Search over the leading page uses its context and controls; it does not borrow unfinished values from the trailing editor. A selected destination uses the resolved editing Request's Display Template relationship after WorkflowRequest or EditRequest resolution, including direct entry into another section. Rendering, prompt resolution, and subscription updates do not start a search or invoke a link.

### Navigation context supplied to AI

Owner: [protocols.md#navigation-context-supplied-to-ai](../reference/protocols.md#navigation-context-supplied-to-ai)

- `ai.context.meaning`: Derive candidate meaning from the bound schemas, extracted documents, and current data. Resolve StringResources and FormattedText through the normal rendering rules. A Boolean or null row value is sent as its field's declared [answer label](../reference/documentation.md#answer-labels-in-ai-descriptions) where one exists. Retain schema descriptions even when a template changes the visible label. Follow the actual selected members and bound occurrences; collecting only a generic request description loses the destination's meaning. Candidate request schemas may be projected to the descriptions and typed values needed for selection; the full wire payload is not an AI input.
- `ai.context.keep-secrets`: Supply enough context to distinguish all offered candidates, rather than dumping the package's unrelated schemas or all return data. Keep credentials, authentication identifiers used only for construction, and opaque continuation values in the client. A domain identity needed to distinguish links can use its ordinary meaningful display value. Treat user and response text as data, not as authority to change the selection contract.
- `ai.context.member-description-once`: Within an entry, a finite chooser's selected member and group descriptions appear once; do not append the chooser's concatenated path description again.
- `ai.context.destination-identity`: Two destinations are the same when they construct the same target request variant with the same selected chooser members and bound values.
- `ai.context.shared-documentation-intact`: The size limit is a check on this semantic projection, not a reason to truncate the eligible candidate set or send a full request. This projection is local to Search. Preserve the source comments and extracted documents used by ordinary field help, manual chooser filtering, Assistant tool descriptions, AI Import, and contextual dictation; reducing a search payload must not shorten their shared documentation. Dictation must still receive the offered destination names and documented aliases from the projected meaning.

### Selection result and invocation

Owner: [protocols.md#selection-result-and-invocation](../reference/protocols.md#selection-result-and-invocation)

- `ai.select.alternatives`: Multiple candidates are alternatives, never a batch to execute: show their ordinary destination labels and relevant distinguishing context, and invoke only the one the person selects. A provider adapter may encode this logical result in its supported structured-output format. The result contains no request payload or field patch. Reject unknown or repeated identifiers, additional request values, malformed output, and results belonging to an invalidated selection attempt. Model failure or no match leaves the current page in place.

### Shared CopyFrom and CopyTo rules

Owner: [schema.md#shared-copyfrom-and-copyto-rules](../reference/schema.md#shared-copyfrom-and-copyto-rules)

- `calc.inputs.unfinished-not-defaulted`: An unfinished value in a mapped calculation source is also distinct from a destination-only field: ordinary defaults must not replace that missing input. The [calculation completeness rules](../reference/protocols.md#reduced-record-and-array-copies) determine whether the constructed request can be sent.

### Typed ancestors and bound occurrences

Owner: [schema.md#typed-ancestors-and-bound-occurrences](../reference/schema.md#typed-ancestors-and-bound-occurrences)

- `calc.binding.belongs-to-operation`: Bindings belong to the operation. A later reply cannot retarget another item because it occupies the same index; different root occurrences remain distinct calculation instances, and different link invocations distinct constructions. The calculation contract owns scheduling, invalidation, atomic assignment, and deferred re-execution.

### User and group defaults

Owner: [schema.md#user-and-group-defaults](../reference/schema.md#user-and-group-defaults)

- `user.default-resolution`: Resolve built-in identities from Hub metadata and current-user identities from the authenticated session, never from display names. Expand current-user groups in ascending numeric ID order, removing duplicates; then process the complete selector sequence left to right, keeping each ID's first occurrence. Missing required Hub, session, or membership context leaves the whole default unresolved (undefined), never a partial selection. A resolved empty membership yields an empty group expansion. Final values still satisfy ordinary field constraints.

### Fixed current-user security constraint

Owner: [schema.md#fixed-current-user-security-constraint](../reference/schema.md#fixed-current-user-security-constraint)

- `security.current-user.trusted-binding`: The same requirement applies to ordinary requests, subscription requests, workflow submissions, calculation requests, and AI tool calls. An App-to-App or forwarded invocation without a trusted authenticated user binding MUST be rejected when it requires this constraint; the App owner or a user ID copied from another invocation is not a substitute. Copies and supplied EditRequest values retain their bytes and must pass the constraint when submitted. Stored results do not constitute proof of the current caller's identity.
- `security.current-user.resolved-submission`: The rule uses effective attributes, including aliases, inheritance, and materialized dependencies. Every submission must carry the resolved current user ID. A client that cannot resolve it must leave the request unfinished; Hub admission rejects it rather than assuming a user or weakening the check.

### MIME-aware Bytes

Owner: [schema.md#mime-aware-bytes](../reference/schema.md#mime-aware-bytes)

- `media.web.storage-prerequisite`: Android, iOS, Studio, and the pure Web Control client implement this contract. The pure Web client requires origin-private file storage to retain selected media links across page reloads; without it, file selection uses an in-memory value for the current editor and camera capture is unavailable.
- `media.link.reject-malformed-codec`: `mime_length` is 3–255. The MIME string is exact lowercase `type/subtype`, matches the effective filter, and has no parameters. The enclosing Avro length delimits the content; there is no content-length field, filename, local path, or file-link token in the wire value. A decoder rejects malformed, truncated, unknown-version, or disallowed nonempty bytes. JSON transports may Base64-wrap the entire sequence.
- `media.files.no-empty-substitution`: Clients may hold the field as undefined, as a client-local file link, or as nonempty in-memory bytes. Server-sent full values use the in-memory form; server-sent empty bytes become undefined. A selected file or camera image is staged privately and held by a local link. Every outbound serialization opens and validates linked files, then materializes the complete LMB1 value before sending. If a link disappears during submission, the client stops that attempt, marks the field undefined, reports the field error, and retains other edits. It never silently submits empty bytes for a disappeared file. The [local draft rule](../reference/annotations.md#mime-aware-bytes-in-workflow-drafts) governs recovery and cleanup; [media presentation](../reference/presentation.md#mime-aware-bytes-presentation) governs the default controls and preview.

### 5.3 Enumerations

Owner: [schema.md#53-enumerations](../reference/schema.md#53-enumerations)

- `protocol-variant.client-matching`: Several enum members may name the same protocol variant. Clients first search the current editing Request's typed value tree for enums whose declarations contain that association, including Hidden fields. If present, their selected marked members identify the values to match in each eligible linked occurrence of the leading page. Match the same resolved enum declaration and selected member through its declared numeric value, independently of field names or positions. For example, `TaxSection.Wages` and `TaxSection.Retirement` can both name `SaveIncomes`: its `topic: TaxSection` value identifies the active income row. An absent, invalid, or unmarked value in such a Request enum matches no row; never replace it with the first choice. If the Request contains no enum whose declaration associates with its variant, the association to the Request variant alone may identify a unique linked occurrence. Both forms use the actual editing Request, including direct WorkflowRequest and EditRequest entry. Exactly one matching linked occurrence is highlighted; no match, unmapped or invalid enum values, or multiple matching occurrences clear the selection. Recompute against every loaded or refreshed leading page, so reordering never retains an old position. Hidden identity fields may identify a visible linked occurrence, but hidden nodes and their descendants never become visible or selectable. The [pane contract](../reference/presentation.md#master-detail-panes) owns eligible linked occurrences and placement: association affects optional highlighting, never pane membership or whether an editor remains usable.

### 6.1 Endpoint protocol validation

Owner: [schema.md#61-endpoint-protocol-validation](../reference/schema.md#61-endpoint-protocol-validation)

- `task-editor.single-candidate-fill`: Every task editor uses the same filtered candidate sets for manual selection and initialization. After a `ScriptValue` is created or populated, the editor visits every uninitialized leaf in the editable value tree of type `Device`, client `Endpoint`, or `Enumeration`. It fills a leaf only when exactly one valid candidate remains. Device-type filters, endpoint protocol compatibility, and the full `Unique` ancestor-list scope rule are applied before counting. The `Unique` value is the number of ancestor `List` scopes to check, with `99` as the default. Zero or multiple candidates leave the value uninitialized for explicit user choice. `Unique` never affects leaves outside its deterministic ancestor-list scope. Each creation or population event causes one whole-tree traversal so every uninitialized leaf is evaluated against the values currently present in its own scope. User selection follows [User and group defaults](../reference/schema.md#user-and-group-defaults) and is not inferred from the candidate count.

### 6.2 Task pre-creation static dependency check

Owner: [schema.md#62-task-pre-creation-static-dependency-check](../reference/schema.md#62-task-pre-creation-static-dependency-check)

- `tasks.dependency.static-plus-authoritative`: Before transmitting a task-creation request, every Android or iOS control client runs the following ordered gate against the selected Hub. This is a client-side static check in addition to, never a replacement for, the Hub's authoritative transactional validation.
- `tasks.dependency.singleton-first`: Check the App-and-native-function `Singleton` identity first. On conflict, report the failure and stop immediately. Do not queue the request and do not start dependency installation.
- `tasks.dependency.occurrence-aware-traversal`: Snapshot the requested task configuration into an immutable client-local pending-creation record, then traverse the complete effective function-input schema tree. Follow materialized named types, inherited and compatible schema nodes, Tables, Lists, and every Union alternative. Traversal must be cycle-safe, but a globally visited type set must not suppress another occurrence reached by a different field path because ancestor optionality is path-dependent.
- `task-create.required-endpoint`: Inspect every client `Endpoint` carrying `Default` or `Fixed`. An occurrence is absolutely required when neither that Endpoint nor any node on its path to the function root has effective `Nullable`, and no ancestor source array or schema `List` permits zero items. A `List` permits zero when effective `SizeMin` is absent or zero; a strictly positive `SizeMin` does not. No other ancestor kind, including selection of a Union alternative, makes the occurrence optional for this static check.
- `tasks.dependency.live-eligible-set`: For every absolutely required occurrence, calculate the live eligible server set from the Hub's current task/Endpoint snapshot. Apply complete mutual protocol-schema compatibility first. `Default` leaves every compatible existing server eligible. `Fixed` additionally requires the server's owning task process to equal its exact `developer-package::function` value. The requirement is satisfied only when this final set contains at least one server.
- `tasks.dependency.install-missing-process`: If any requirement is unsatisfied, present the required-task installation UI for the process named by that Endpoint's `Default` or `Fixed` value. Requirements may be grouped by identical process identity for presentation, but every Endpoint path remains independently checked and all requirements must be satisfied simultaneously. Installing the package alone is not sufficient: the resulting task must expose a server that passes the same compatibility and, for `Fixed`, identity filters. If the named App package is absent, clients use the current Libertas Plaza flow to install that exact package before opening its required-task editor. A legacy Hub without Plaza support can create the dependency task only from an already installed App.
- `tasks.dependency.queue-until-complete`: The original creation record remains queued while required installations run; no task-create/add-update request is sent for it. Creating a dependency task runs this same gate and may therefore enqueue its own dependency chain. The client tracks the originating root request and rejects a cycle of unsatisfied required task-process dependencies instead of recursively opening installation UI forever.
- `tasks.dependency.recheck-after-install`: After each successful installation, the client refreshes the authoritative Hub task/Endpoint snapshot and reruns the entire gate beginning with `Singleton`. It must not resume from cached candidate IDs or assume installation alone met a requirement. Only one fresh reevaluation that simultaneously satisfies every requirement may dequeue and transmit the original request. Cancelling any dependency installation in that request's chain immediately cancels and removes the originating creation record; a later installation or topology change must not resurrect it. Other independent queued task creations are unaffected.

### 7. Path-based object and choice binding

Owner: [schema.md#7-path-based-object-and-choice-binding](../reference/schema.md#7-path-based-object-and-choice-binding)

- `baseobjects.task-editor`: Android and iOS task editors retain a server endpoint with `BaseObjects` but omit its separate editable row. The same ordered naming and icon rules apply; the endpoint remains eligible as a compatible server choice for client endpoints.

### 8. Types-only and interoperable packages

Owner: [schema.md#8-types-only-and-interoperable-packages](../reference/schema.md#8-types-only-and-interoperable-packages)

- `schema.types-only.reject-code-mismatch`: the Hub must reject marker/code mismatches and must not create an executable artifact for the version.

### 10. Localized package documents

Owner: [schema.md#10-localized-package-documents](../reference/schema.md#10-localized-package-documents)

- `docs.persisted-text.absolute-priority`: Client initialization order is normative. Persisted or user-assigned text has absolute priority and cannot be changed programmatically by default application, locale/document/schema refresh, migration, or upgrade. A saved empty string is also persisted text and must remain empty. Only when no persisted value exists may a client initialize a new String or Endpoint-server presentation value. Where the [shared copy rules](../reference/schema.md#shared-copyfrom-and-copyto-rules) leave that node to ordinary initialization, an applicable schema `Fixed`/`Default` value wins, localized `DefaultText` is the next fallback, and the ordinary empty or localized field-label fallback is last. For a new blank task name, localized `DefaultText` wins, followed by the localized function `DisplayName`, then the native function name.

### 11.3 Revision digest

Owner: [schema.md#113-revision-digest](../reference/schema.md#113-revision-digest)

- `schema.revision.regenerate-stale-digest`: Both timestamp and digest are validated before parity comparison. A missing, malformed, or stale digest requires regeneration.

### 2. Data Model

Owner: [validation-rules.md#2-data-model](../reference/validation-rules.md#2-data-model)

- `validation.editor.total-on-incomplete-null`: Structural schema compliance is outside the scope of the validation-rule language. An evaluator MUST nevertheless tolerate the transient `null` values used by client editors and MUST remain total for that incomplete state.

### Debugging rules

Owner: [validation-rules.md#debugging-rules](../reference/validation-rules.md#debugging-rules)

- `validation.debug.only-evaluated-branches`: The trace records evaluated paths as typed values, unanswered reads, absent values, or ordered List fan-out, and records evaluated operator and function results, including `|`, `is`, and `has`. Numeric results retain exact rational values; date results retain their semantic type. Skipped branches and cursor iterations are never evaluated for debugging. Developer evaluation may inspect all applicable rules independently in declaration order while returning the same first violation as ordinary evaluation. Without a requested trace, ordinary evaluation retains its existing work and first-failure behavior.
- `validation.debug.no-values-in-ui`: The control view's developer-only **Why** disclosure shows this trace under a failing or undecided rule. Expressions and entered values never appear in normal user presentation. Debugging changes neither stored data nor client protocols, and a trace does not replace structural validation.

### 13.2 Full-string patterns

Owner: [validation-rules.md#132-full-string-patterns](../reference/validation-rules.md#132-full-string-patterns)

- `validation.matches.ordered-evaluation`: Runtime evaluation is ordered: evaluate arguments left-to-right and propagate argument errors; reject non-null wrong types or ill-formed scalar Strings; return null if either remaining argument is null; otherwise compile and match. An invalid dynamic pattern with a null value therefore returns null before compilation, while a statically known bad pattern is always rejected by Studio. With two non-null Strings, compilation precedes any empty-text shortcut.

### Portable regex language

Owner: [validation-rules.md#portable-regex-language](../reference/validation-rules.md#portable-regex-language)

- `validation.regex.non-backtracking`: Require non-backtracking regular-language matching, such as Thompson NFA, including for nested/ambiguous repetition.

### ValidationRules literal layer

Owner: [validation-rules.md#validationrules-literal-layer](../reference/validation-rules.md#validationrules-literal-layer)

- `validation.source.no-extra-unescape`: The earlier Rust and TypeScript digit examples transmit the same regex. Do not add another source-unescape pass to the existing TypeScript ValidationRules annotation as an incidental change.

### Regex layer and exact-value examples

Owner: [validation-rules.md#regex-layer-and-exact-value-examples](../reference/validation-rules.md#regex-layer-and-exact-value-examples)

- `validation.serialization.decode-once`: Regex `\d+` serializes as `{"Name":"Pattern","Value":"\\d+"}`; the decoded Value contains one backslash, d, plus. Regex matching a literal backslash serializes as `{"Name":"Pattern","Value":"\\\\"}`; decoded Value contains two backslashes. Do not decode either serialized spelling again.

### 16. Static Rule Validation

Owner: [validation-rules.md#16-static-rule-validation](../reference/validation-rules.md#16-static-rule-validation)

- `validation.paths.impossible-path-review` (judgment): A path that cannot exist under any value permitted by the schema SHOULD be rejected as a rule-definition error.

### 18. Validation Result

Owner: [validation-rules.md#18-validation-result](../reference/validation-rules.md#18-validation-result)

- `validation.result.message-owns-explanation`: The supplied message owns the explanation. Clients do not construct, rank, or render structural failure certificates. If a trusted installed document is unexpectedly missing the message or supplies only whitespace, report a generic validation failure; do not expose expressions or entered values as a fallback.
- `validation.result.final-failure-ui` (judgment): On final validation failure, keep the editor and its data. When the validator identifies a failing field or a rule-owning group, show an error icon and the explanation at that row, expand collapsed ancestors, and scroll the first failure into view. A promoted header uses its owning row; a hidden field uses its nearest visible owner. Do not replace the field’s normal control with an inline editor: Boolean choices, enumerations, Unions and other pickers retain their normal interactions. Function-wide rules without a field owner remain message-only; do not infer field blame from an expression.
- `validation.error-marker.occurrence-lifetime`: The marker is transient client state, not encoded data. After an accepted edit, recheck the marked field and clear its marker when its validation passes or when that field is removed. Do not transfer the marker to a replacement List row or Union branch. A later Submit may reveal the next failure. These local markers do not change backend Error responses, which retain the protocol’s message presentation.
- `validation.incremental.every-change`: A client Task Editor MUST evaluate every rule set whose effective `FinalOnly` value is `false` (including an omitted directive) after every proposed node change. One synchronous UI action MAY be treated as one atomic change when it necessarily performs several model mutations, such as selecting a Union variant and materializing its fields. Node changes include scalar and byte replacement, nulling or materialization, Union selection, and List insertion, removal, replacement, or reordering.
- `validation.incremental.snapshot-restore`: The client MUST snapshot the complete editable root set before the change, because a field-local change can invalidate an ancestor, named-type, or function rule. If incremental validation fails, the client MUST restore that snapshot before presenting the violation as a client-native error toast. The invalid value MUST NOT remain in task data. Declarations whose effective `FinalOnly` value is `true` are not evaluated and cannot reject an incremental change.
- `validation.incremental.no-static-checks`: Incremental validation does not run mandatory/nullability or other static final checks.
- `validation.incremental.introduced-violation`: This rollback applies to the edits a user makes, on every client. An edit is rolled back when it introduces a violation: when the first failing rule and its owning node are the same before and after the edit, for example because a calculation result left that rule violated, the edit is accepted, and final validation still blocks submission.
- `validation.incremental.programmatic-assignment`: A programmatic assignment is not a proposed change and is never rolled back by an incremental rule: a calculation result, a link return, a constrained-choice reset, and a singleton selection stay in the editor, and final validation governs them.
- `validation.final.static-and-rules`: The client MUST evaluate all rule sets as part of final validation; a successful incremental or final rule result cannot override `StaticValid=false`.

## Rules Studio enforces

Studio rejects a source that breaks one of these rules. The owner holds the full rule.

<!-- /generated -->
