Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b9110ed
spec(AST-099): one shared on-screen keyboard module measures the keyb…
cixzhang Oct 24, 2026
90e83f3
spec(AST-099): the keyboard height stays internal; consumers apply th…
cixzhang Oct 24, 2026
5c9eee7
spec(AST-099): observable contract with the mechanism in react-compon…
cixzhang Oct 24, 2026
d533424
spec(AST-099): layer geometry stays with AST-059; Lab conformance in …
cixzhang Oct 24, 2026
18b235a
spec(AST-099): height lives only in the hook; Lab conformance agrees …
cixzhang Oct 24, 2026
73e9917
spec(AST-099): FR8 in FR6 terms with Firefox for Android; INV11 bans …
cixzhang Oct 25, 2026
2200b18
spec(AST-099): keyboard motion uses transform only, with one layout c…
cixzhang Oct 25, 2026
40a306d
spec(AST-099): keyboard motion is one compositor animation to the pre…
cixzhang Oct 25, 2026
0ed980b
spec(AST-099): one published transition every consumer rides in sync
cixzhang Oct 25, 2026
94ce1d5
spec(architecture): INV11 wording for the height and transition
cixzhang Oct 25, 2026
5d81b92
spec(AST-099): observable ride in FR3, transition mechanism in INV11;…
cixzhang Oct 25, 2026
5e05908
spec(AST-099): name Firefox for Android in platform lines and OQ1; IN…
cixzhang Oct 25, 2026
b46535c
spec(AST-099): name the Core keyboard module; DEC-2 Chrome for Androi…
cixzhang Oct 25, 2026
9e6c93e
spec(AST-099): Chrome on Android rides the keyboard smoothly (DEC-2 a…
cixzhang Oct 25, 2026
592d352
spec(AST-099): FR8 names Chrome's keyboard report, scopes the ride to…
cixzhang Oct 25, 2026
3c9ad90
spec(AST-099): FR8 covers any browser that reports the keyboard; redu…
cixzhang Oct 25, 2026
1926fd9
spec(AST-099): FR1, FR3, INV11 and platform lines accept FR8 wherever…
cixzhang Oct 25, 2026
986dad0
spec(AST-099): Firefox for Android row proves FR2–FR5 states
cixzhang Oct 25, 2026
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
50 changes: 38 additions & 12 deletions docs/architecture/react-component-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ authority: current
archive_reason: null
superseded_by: null
approved_by: cixzhang
approved_at: 2026-09-07
approved_at: 2026-10-24
owners: [cixzhang]
applies_to: [packages/core/src/, packages/lab/src/]
verified_by:
[
packages/core/src/Layer/useLayer.test.tsx,
packages/core/src/utils/sharedResizeObserver.test.ts,
]
deciding_specs: [spec:AST-086/DEC-2]
deciding_specs: [spec:AST-086/DEC-2, spec:AST-099/DEC-1]
---

# React component runtime architecture
Expand All @@ -34,7 +34,8 @@ deciding_specs: [spec:AST-086/DEC-2]
"INV5",
"INV6",
"INV8",
"INV10"
"INV10",
"INV11"
]
}
}
Expand Down Expand Up @@ -136,6 +137,28 @@ those contracts.
adopting components hold no Effect, observer, timer, or
listener of their own to detect page load, read rendered text, or write that
announcement. The module follows INV3 and INV5.
- **INV11 — The on-screen keyboard has one owner.** In `@astryxdesign/core`,
one shared internal hook in the keyboard module (`packages/core/src/keyboard/`)
measures the on-screen keyboard, keeps the page
from panning on focus, and dismisses the keyboard when a surface moves
(`spec:AST-099`); it is not exported. The keyboard height and each keyboard
change's transition (start time, resting value, curve) reach styles only as
the hook's value and live only in the hook: each consumer applies them
through a StyleX functional style on its own element, and no CSS custom
property on the document root or a shared ancestor carries them. No Core
component keeps its own visual-viewport listener for the keyboard. The hook
publishes each keyboard change as one transition: a start time, a resting
value, and a curve. Every Core consumer that rides the keyboard rides that
transition with compositor animations, a translate to the resting value,
never JavaScript per frame, and the hook makes at most one layout read per
keyboard change. A component outside Core does not ride; it commits the
resting value once. Packages outside Core cannot import the hook; a
Comment on lines +154 to +155

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 no-ride rule now conflicts with spec:AST-099 FR8, which requires every keyboard-aware surface in Chrome on Android to ride; the FR2 measurement prescribed a few lines below is also switched off there by FR6. Whichever way the owner decides, the two records need to agree.

component there that responds to the keyboard keeps one listener that
measures as `spec:AST-099` FR2 defines, or takes the browser's keyboard report
where FR8 applies, keeps focus from panning as FR4
does, blurs as FR5 does, applies the height through a functional style on
its own elements, and follows INV3 and INV5. The hook follows INV3 and
INV5.

## Authoring guidance

Expand Down Expand Up @@ -255,6 +278,8 @@ contracts.

- `spec:AST-086/DEC-2` — own-content announcements behave the same in every
component that announces its own rendered content (INV10).
- `spec:AST-099/DEC-1` — one keyboard height for every component, kept
internal and applied through functional styles (INV11).

Component, family, API, accessibility, layer, interaction, and style records
remain authoritative for the other user-facing outcomes delegated above.
Expand All @@ -268,12 +293,13 @@ observable-behavior predicates require review or runtime evidence. Likewise, a
focused test proves only its named behavior and mutation expectation; a green
component suite is not blanket conformance with every invariant.

| Invariant | Evidence | Failure signal |
| ---------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| INV1–INV2 | Initial-render and interaction tests | Output flashes a known-wrong state, or one action is delayed or repeated through an Effect |
| INV3, INV5 | StrictMode setup/cleanup and dependency-change tests | Duplicate callback, leaked work, stale subscription, or missing resynchronization |
| INV1, INV4, INV6 | Ref attach/null/replacement, DOM-in-state rejection, and old-node cleanup-before-new-attach mutations | A DOM node or collection enters state, old node remains active, cleanup misses replacement, or ref mutation changes semantics silently |
| INV7 | Real-browser geometry plus N-consumer observer-instance/subscription mutations | Geometry is invalid, observer instances grow with repeated consumers, one unsubscribe silences peers, or cleanup removes another subscriber |
| INV8 | Owner-observable behavior across documented composition replacement; optional focused internal mechanism tests | Behavior disappears or changes owner when a documented subpart is replaced |
| INV9 | Provider/external-store identity and render-count tests | Unchanged semantic payload changes identity, or snapshot identity creates a render loop |
| INV10 | Shared test over every adopter of the announcement module | An adopter holding its own Effect, observer, timer, or listener for its own-content announcement |
| Invariant | Evidence | Failure signal |
| ---------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| INV1–INV2 | Initial-render and interaction tests | Output flashes a known-wrong state, or one action is delayed or repeated through an Effect |
| INV3, INV5 | StrictMode setup/cleanup and dependency-change tests | Duplicate callback, leaked work, stale subscription, or missing resynchronization |
| INV1, INV4, INV6 | Ref attach/null/replacement, DOM-in-state rejection, and old-node cleanup-before-new-attach mutations | A DOM node or collection enters state, old node remains active, cleanup misses replacement, or ref mutation changes semantics silently |
| INV7 | Real-browser geometry plus N-consumer observer-instance/subscription mutations | Geometry is invalid, observer instances grow with repeated consumers, one unsubscribe silences peers, or cleanup removes another subscriber |
| INV8 | Owner-observable behavior across documented composition replacement; optional focused internal mechanism tests | Behavior disappears or changes owner when a documented subpart is replaced |
| INV9 | Provider/external-store identity and render-count tests | Unchanged semantic payload changes identity, or snapshot identity creates a render loop |
| INV10 | Shared test over every adopter of the announcement module | An adopter holding its own Effect, observer, timer, or listener for its own-content announcement |
| INV11 | Source boundary check plus hook tests across StrictMode replay and unmount | A second keyboard listener or measurement in Core; a consumer reading a CSS custom property on the root or a shared ancestor for the height; an export of the hook; a component outside Core measuring by a rule other than AST-099 FR2 or, where it applies, FR8, or riding the keyboard; a listener left after unmount; a Core rider driven by JavaScript per frame or by its own transition; or more than one layout read per keyboard change |
Loading
Loading