Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/architecture/public-component-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ deciding_specs:
spec:AST-012/DEC-2,
spec:AST-071/DEC-1,
spec:AST-017/DEC-23,
spec:AST-012/DEC-6,
]
---

Expand Down Expand Up @@ -165,6 +166,12 @@ guidance owns the process used to propose and test APIs.
When a touch default changes a released component's behavior, that
`presentation` value is the restoring prop `spec:AST-017` FR3 names
(`spec:AST-017/DEC-23`).
- **INV17 — Compact has one shared definition.** A component that chooses a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A component that chooses a presentation by screen size" has no boundary: AppShell picks its mobile layout by a caller-chosen width point with no height edge under spec:AST-012 FR8. Say whether that layout is in scope, for example by limiting INV17 and FR9 to components that resolve a popover, bottom-sheet or drill-in presentation.

presentation by screen size uses the theme's compact definition: width below
the effective `md` point or height below 500, with the same numbers on every
platform (`spec:AST-012` FR9). It keeps no compact numbers of its own, as
INV14 keeps no parallel breakpoint table. A layout width the caller picks,
such as AppShell's `mobileNav.breakpoint`, follows INV14 instead.

This record applies to stable public packages. Lab components are not stable
public promises until promotion.
Expand Down Expand Up @@ -199,7 +206,8 @@ public promises until promotion.
evidence required by the release process.
- A component exposing a named theme width point uses the fixed vocabulary and
exclusive upper-edge behavior from `spec:AST-012`; it does not maintain a
parallel hardcoded breakpoint table.
parallel hardcoded breakpoint table. A component that chooses a presentation
by screen size uses the compact definition (INV17).
- Changes to a family-owned API update the family contract rather than copying
its rule into this record or every component.
- A component that accepts or derives a navigation destination follows
Expand Down Expand Up @@ -227,6 +235,7 @@ public promises until promotion.
- `spec:AST-017/DEC-23` — a touch presentation or open-surface touch mode is
a default the caller can always override, through one `presentation` prop
that also restores the previous surface and mode.
- `spec:AST-012/DEC-6` — compact is one definition, the same on every platform.

Transition Action sequencing and pending behavior belong to their component or
family contract. This record links that owner once it is current; it does not
Expand All @@ -246,6 +255,7 @@ copy the component matrix.
| INV14 | `AppShell.test.tsx` | A component hardcodes a divergent point or treats equality as below |
| INV15 | Public-API review of the generated declaration inventory | A part or StyleX-typed styling prop ships without class-name and inline-style counterparts on the same element |
| INV16 | Public-API review of touch presentations plus coarse- and fine-pointer runtime tests | A stable component that presents a different surface or switches an open surface's input mode under a coarse pointer and has no `presentation` value that keeps its fine-pointer surface and mode; such a touch mode callers cannot reach; a default that overrides an explicit caller choice |
| INV17 | Resolver tests at the compact edges plus public-API review | A component that chooses a presentation by screen size with its own numbers, or disagrees with the theme compact definition at width 768 or height 500 |
| Consumer-doc projection | `docPropReferences.test.ts` and `docPropLiterals.test.ts` | Docs name a nonexistent prop or omit public literal choices |

Known verification gaps:
Expand Down
63 changes: 51 additions & 12 deletions docs/specs/AST-012/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authority: current
archive_reason: null
superseded_by: null
approved_by: cixzhang
approved_at: 2026-09-02
approved_at: 2026-10-24
phase: accepted
owners: [cixzhang, rubyycheung, imdreamrunner]
affects_architecture:
Expand Down Expand Up @@ -98,11 +98,13 @@ A breakpoint map alone emits no CSS. Only rules with adaptation values do.
base below `sm`; there is no configurable `xs: 0` point. Defining or overriding
the map alone MUST NOT emit CSS.
- **FR2 — Conditions are named, closed, and composable.** A `when` object may
contain `width`, `pointer`, `contrast`, and `motion`; fields are ANDed.
contain `width`, `height`, `pointer`, `contrast`, and `motion`; fields are
ANDed.
`width` accepts `from` and `below` using the five fixed names and MUST contain
one or both. `from` is inclusive, `below` is exclusive, either edge may be
omitted but not both, and a bounded width MUST resolve to `from < below`.
`pointer` accepts `coarse | fine`; `contrast` accepts
`height` accepts only `{below: 500}`: a viewport height below 500, exclusive,
the height edge of the FR9 compact definition. `pointer` accepts `coarse | fine`; `contrast` accepts
`more | less | no-preference`; `motion` accepts `reduce | no-preference`.
Future environment fields require explicit additions to this closed vocabulary.
- **FR3 — Rules are named objects.** `adaptations.rules` contains
Expand Down Expand Up @@ -161,6 +163,20 @@ A breakpoint map alone emits no CSS. Only rules with adaptation values do.
`architecture:theme-application`, and with no active theme it uses the FR1
defaults. `defaultIsMobile` remains an SSR hint; the client query uses the
effective map after hydration.
- **FR9 — Compact has one definition on every platform.** A screen is compact
while its viewport width is below the effective `md` point (FR1) or its

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking: "the effective md point" does not say which theme supplies it. FR8 gives AppShell the nearest provider, the root-theme fallback and the FR1 defaults, and its verification row tests no, nearest, root and nested themes; the same sentence here and those states in the FR2 (height), FR9 row would keep two resolvers from reading different themes.

viewport height is below 500. The numbers are the same on every platform: CSS
pixels on the web, points on iOS, and density-independent pixels on Android;
native presentations do not map compact to platform size classes. The theme

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This native sentence (and @astryxdesign/native on line 173) conflicts with current records this PR leaves unchanged: spec:AST-082 FR18 puts adaptations, where widthBreakpoints and so md live, in the web part that native themes report as having no native meaning, and AST-082 FR11 with spec:AST-093 FR3 leave native presentation to platform controls and conventions. Amend those records in this PR, or scope FR9 to the web.

exports this one definition, and every resolver that chooses a presentation
by screen size uses it, including the web touch presentation resolver,
Selector's `adaptive` presentation, and `@astryxdesign/native`; none keeps
size numbers of its own. A resolver that also needs a coarse pointer ANDs
that with compact. Compact governs only the choice of a presentation, such as
a sheet in place of a popover; a layout width a caller picks, such as
AppShell's `mobileNav.breakpoint` (FR8), is not compact. Theme `adaptations`
stay web-only (`spec:AST-082` FR18); native reads the compact numbers from
the portable theme.
- **IR1 — Normalize and reject atomically.** One path validates width breakpoints,
named condition fields, width ranges, theme extension, enrolled local-token
names, and concrete expanded values for both `defineTheme` and
Expand Down Expand Up @@ -197,8 +213,8 @@ A breakpoint map alone emits no CSS. Only rules with adaptation values do.

### Platform support

- Adaptations use viewport-width, primary-pointer, contrast-preference, and
reduced-motion queries within Astryx's browser support floor; there is no
- Adaptations use viewport-width, viewport-height, primary-pointer,
contrast-preference, and reduced-motion queries within Astryx's browser support floor; there is no
JavaScript styling fallback.
- Real Chromium MUST verify conditions, nesting, AppShell agreement, and first
paint. Real Safari MUST verify exclusive width edges and primary-pointer
Expand All @@ -223,15 +239,25 @@ implemented.
Its value resolver, explicit-value preservation, CSS generation/build parity, and
extension metadata remain useful implementation evidence.

The theme exports no compact definition, and `when` has no `height` field.
Selector's, MultiSelector's, ContextMenu's, and DropdownMenu's `adaptive`
presentation (MoreMenu forwards to DropdownMenu) and DropdownMenu's compact-touch
sub-menu decisions use `(max-width: 768px) and (pointer: coarse)`, and the
ContextMenu and MoreMenu consumer docs describe it as "768px and below": a coarse-pointer screen exactly 768 pixels wide and at least 500 tall counts as compact, and a landscape phone wider than 768 pixels but under
500 tall does not. Under FR9 the first opens the popover and the
second the bottom sheet; the release note for that change names `presentation`
as the way to keep a surface. `PortableTheme` carries no compact definition, and `@astryxdesign/native` has no compact resolver yet; it adopts FR9 when it gains one.

## Verification

| Contract | Verification | Representative states | Failure signal |
| -------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| FR1–FR3 | Width-breakpoint and condition matrix plus browser boundaries | defaults/overrides; from/below/range; pointer/contrast/motion; multi-field AND; empty/no rules | A point acts as an upper bound, intervals gap/overlap, fields OR together, or an unused map emits CSS. |
| FR4–FR7 | Shared resolver, surface, cascade-order, and extension tests | enrolled/unenrolled local tokens; incomplete scales; finite built-ins; open primitive values absent from root; root-enrolled and rule-only custom values; unresolved shared results; later generated vs earlier explicit leaves; surface collisions | A rule enrolls a local or component value, a valid open value requires a dummy root rule, adaptation validation diverges from root validation, an unresolved result becomes a permanent exemption, or inherited order is lost. |
| FR8 | Theme/AppShell integration | all names/`none`; SSR hint; no/nearest/root/nested theme; exact boundaries | AppShell uses the wrong map, equality, or scope. |
| IR1–IR3 | Runtime/build and CLI positive/negative fixtures | invalid map/range/field; source axis metadata; duplicate `when`; root-restoring rules; finite/open/unresolved component domains; declaration targets; root-enrolled and rule-only custom values; non-CSS token reads | Invalid input writes output, root and adaptation component validation disagree, an independently valid value is forced into root, an adaptation conditionally enrolls a custom value, or tooling misses a rule-only value. |
| IR4 | Generated-module import and size tests | no rules/rules; source/built parent and child; generative/local-token metadata | Breakpoints, rules, order, or metadata disappear; extension diverges; CSS text enters the module; or resolved layers duplicate. |
| Contract | Verification | Representative states | Failure signal |
| ----------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| FR1–FR3 | Width-breakpoint and condition matrix plus browser boundaries | defaults/overrides; from/below/range; pointer/contrast/motion; multi-field AND; empty/no rules | A point acts as an upper bound, intervals gap/overlap, fields OR together, or an unused map emits CSS. |
| FR4–FR7 | Shared resolver, surface, cascade-order, and extension tests | enrolled/unenrolled local tokens; incomplete scales; finite built-ins; open primitive values absent from root; root-enrolled and rule-only custom values; unresolved shared results; later generated vs earlier explicit leaves; surface collisions | A rule enrolls a local or component value, a valid open value requires a dummy root rule, adaptation validation diverges from root validation, an unresolved result becomes a permanent exemption, or inherited order is lost. |
| FR8 | Theme/AppShell integration | all names/`none`; SSR hint; no/nearest/root/nested theme; exact boundaries | AppShell uses the wrong map, equality, or scope. |
| FR2 (height), FR9 | Condition matrix plus resolver tests on web and native | width 767/768 at height 800; width 1024 at height 499/500; overridden `md`; Selector `adaptive`; the web touch resolver; a native resolver | A resolver with its own size numbers or an inclusive edge, a native resolver using platform size classes or different numbers, or a `height` value other than `below: 500` accepted |
| IR1–IR3 | Runtime/build and CLI positive/negative fixtures | invalid map/range/field; source axis metadata; duplicate `when`; root-restoring rules; finite/open/unresolved component domains; declaration targets; root-enrolled and rule-only custom values; non-CSS token reads | Invalid input writes output, root and adaptation component validation disagree, an independently valid value is forced into root, an adaptation conditionally enrolls a custom value, or tooling misses a rule-only value. |
| IR4 | Generated-module import and size tests | no rules/rules; source/built parent and child; generative/local-token metadata | Breakpoints, rules, order, or metadata disappear; extension diverges; CSS text enters the module; or resolved layers duplicate. |

## Decision log

Expand Down Expand Up @@ -305,6 +331,19 @@ validation tighten adaptations automatically.
Rejected: adaptation-specific value resolvers, allowlists, unresolved-domain
exceptions, and dummy root rules for independently valid values.

### DEC-6 — Compact is one definition on every platform

**Reference:** `spec:AST-012/DEC-6`
**Decider:** `cixzhang`, `2026-10-24`

A phone, a small tablet, and a short landscape window all lack room for an
anchored surface, so compact is width below the `md` point or height below 500. The same numbers on every platform keep one component presenting the same
way on the web and in native code, and one exported definition keeps resolvers
from drifting apart.

Rejected: mapping compact to each platform's size classes, which makes the
same screen compact on one platform and not another.

## Open questions

None.
Loading
Loading