Skip to content

architecture(layer-runtime): one generic layer runtime, and one opt-in lift-from-trigger module (INV14, INV15) - #8448

Merged
cixzhang merged 9 commits into
mainfrom
spec/layer-runtime-shared-presentation
Oct 26, 2026
Merged

cixzhang merged 9 commits into
mainfrom
spec/layer-runtime-shared-presentation

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Exact decision needed

Does the layer runtime own every layer's shared mechanics, and does lift-from-trigger live in one separate, opt-in module?

Existing authority searched

Decision

architecture:layer-runtime INV14 and INV15 (Cindy, 2026-10-25, 13:18 placement; approved 14:20):

  • INV14: internally, the runtime owns the shared mechanism layers use: top-layer mounting, close-request delivery, the presentation hold, the one shared exit copy (INV7's single Core mechanism), and the backdrop tint via ::backdrop. A component that needs one uses the runtime's, never its own. Each component still decides when it dismisses, how it is hosted, and how it is presented (INV9; overlay-dismissal AV3, AV4, AV6; AST-003 FR10). Public surface stays useLayer and LayerProvider.
  • INV15: lift-from-trigger presentation (lift, grow-from-rect via the rect morph, exit through the runtime's exit copy) lives in one internal module separate from the layer runtime. Surfaces opt in (ContextMenu today); any surface that lifts uses this module, never its own copy; layers that do not lift do not use it. Internal (AST-017 FR21).
  • Numbered INV14 and INV15 so chore(core): the layer runtime's exit copy, for a touch presentation's close #8186's INV13 lands unchanged. INV14 names ownership only; chore(core): the layer runtime's exit copy, for a touch presentation's close #8186's INV13 owns the exit copy's contract (inert, aria-hidden, one per closing layer), and this record does not restate it.

Current-state evidence

Lane 4's audit of the ContextMenu lift (#8201): ContextMenuLift.tsx:257-273 duplicates the rect morph for the menu grow; the clone plus manual popover plus ::backdrop mount (:281, :399-405) are ContextMenu-local.

Rejected alternatives

  • Each lifting surface keeping its own lift, grow, or exit; and one lift module applied to every menu or layer by default.

Claim scope and non-goals

Internal ownership only. No public API changes. Lab's access path to internal modules is a separate, pending decision.

Writing rubric results

  • R1 narration: none.
  • R2 research in the record: none; the audit lines stay in this description.
  • R3 evidence naming a change: none.
  • R4 rejected alternatives over one line or outside a decision: none; architecture records carry no decision log here.
  • R5 pull request or issue references remaining: none.
  • R6 ruled open questions still listing options: none.

Scope

  • This PR contains specification records only.
  • It records observable behavior and preserves equivalent internal implementations.
  • Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract.
  • Verification states the evidence layer and failure signal without prescribing CI job/workflow topology.
  • It records one intentional durable decision, not a rescue for a separable tagalong.
  • The canonical knowledge owner, affected current claims, and overlapping open work are named; any ownership collision is resolved or called out for human decision.
  • Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them.
  • Public text and artifacts contain no internal Meta context.

Validation

  • pnpm check:knowledge (CI lint)
  • Prettier
  • git diff --check

@vercel

vercel Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 26, 2026 12:28am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 26, 2026, 1:30 AM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 25, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 25, 2026
@cixzhang cixzhang changed the title architecture(layer-runtime): proposed: one runtime and one presentation module for every layer architecture(layer-runtime): proposed: one generic layer runtime, and one opt-in lift-from-trigger module Oct 25, 2026
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Previews ready: Storybook and Sandbox were built with this PR's docsite. CI evidence will be added when available.

View Storybook for this PR · View Sandbox for this PR

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This proposal can't be approved yet. INV14 and INV15 add new invariants to the current layer-runtime record, so an engineering owner needs to decide them, including whether INV14 is meant to cover the native-dialog surfaces and ToastViewport. They record that decision by commenting /approve-spec <full head SHA> on this pull request, and the author can do this as an engineering owner. Whatever the decision, these corrections are still needed:

  • Narrow INV14, or amend the claims it contradicts: INV9 and the Owning code lines in this record, the overlay-dismissal family's AV3/AV4/AV6, and AST-003 FR10.
  • Reconcile INV15's clone option with the single-copy rule in component-style-authoring INV7, and state that any copy is inert and hidden from assistive technology.
  • Replace the new ContextMenu gap bullet, because ContextMenu does not lift on main yet; its lift is still pending implementation.
  • Remove the "Proposed; pending owner decision" markers, update the record's shipped-only scope sentence, and define the presentation hold and the shared rectangle morph that the new invariants rely on.

[Automated review]

Comment thread docs/architecture/layer-runtime.md Outdated
Comment on lines +450 to +456
- **INV14 — The runtime owns every layer's shared mechanics.** **Proposed;
pending owner decision.** Internally, the layer runtime owns top-layer
mounting, dismissal, the presentation hold, the one shared exit copy every
layer uses (the single Core mechanism `architecture:component-style-authoring`
INV7 names), and the backdrop tint, drawn with `::backdrop`. No component
mounts its own top-layer host, exit copy, or backdrop element. The public
surface stays `useLayer` and `LayerProvider`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

As worded, INV14 conflicts with claims this record keeps: INV9 (outside channels stay component-owned) and the Owning code lines that give Dialog families their backdrop presentation and Drawer its showModal()/showPopover() hosting. It also conflicts with the overlay-dismissal family's AV3/AV4/AV6 and AST-003 FR10. Dialog, Drawer, BottomSheet, Lightbox, MobileNav and ToastViewport mount their own top-layer hosts today. Either scope INV14 to layers mounted through useLayer, with "dismissal" meaning the Escape and platform-close routing INV8 names, or amend those claims and list these surfaces as gaps.

Comment thread docs/architecture/layer-runtime.md Outdated
Comment on lines +459 to +460
internal module separate from the layer runtime: the lift from the trigger,
using the real trigger or a clone, whichever meets the performance outcomes;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A trigger clone held while the menu is open mounts the trigger's content twice. component-style-authoring INV7 allows a second copy only as the transient copy during an exit, a presentation switch or a sheet handoff. Nothing here says the clone is inert or hidden from assistive technology, and "the performance outcomes" are not defined anywhere in the record.

Comment thread docs/architecture/layer-runtime.md Outdated
Comment on lines +487 to +490
- Under the proposed INV14 and INV15, ContextMenu's touch lift, the one surface
that lifts today, grows its menu
with its own copy of the rectangle morph, and its trigger clone, manual
popover, and `::backdrop` mount are ContextMenu-local rather than shared.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On main, ContextMenu does not lift yet: its record marks the lift as pending implementation, and there is no lift, clone or ::backdrop in its source. This list holds observable facts, and the paragraph below hands its gaps to AST-003, which does not cover lifts.

Comment thread docs/architecture/layer-runtime.md Outdated
| INV8 | `useLayerDismissal.test.tsx`, `layerDismissalInvariants.test.tsx`, and `useFocusTrap.test.tsx` | Current top registered layer is skipped, two layers close, or a blocker leaks through |
| INV9 | Representative Dialog, ContextMenu, Tooltip/HoverCard, and BottomSheet source/tests | A current local channel silently changes ownership or policy |
| INV10 | `LayerProvider.tsx`, `LayerProvider.test.tsx`, `useToast.tsx`, `ToastViewport.test.tsx`, and `ToastViewport.modalHost.test.tsx` | Provider begins relocating ordinary layers, Toast fallback loses its current lifecycle, or a default provider renders differently from no provider |
| INV14, INV15 (proposed) | Source boundary check over Core for top-layer hosts, exit copies, backdrop elements, lift, and rectangle-morph code outside the runtime and the lift module | A component mounting its own top-layer host, exit copy, or backdrop element, or re-implementing the lift or the grow |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No source boundary check like this exists in the repository yet, and verified_by is unchanged; one that did what this row describes would fail today on the dialog-style surfaces. Together with the "Proposed; pending owner decision" markers, this row would merge into a current record that still describes INV14 and INV15 as pending.

@cixzhang cixzhang changed the title architecture(layer-runtime): proposed: one generic layer runtime, and one opt-in lift-from-trigger module architecture(layer-runtime): one generic layer runtime, and one opt-in lift-from-trigger module (INV14, INV15) Oct 25, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

Posted by Cindy's agent on her behalf.

Cindy's decision (2026-10-25): approve. The layer runtime owns the shared mechanism every layer uses (top-layer mounting, close-request delivery, the presentation hold, the one exit copy, the ::backdrop tint), and each component keeps its own dismissal, hosting, and presentation policy (INV14). Lift-from-trigger presentation lives in one internal module separate from the runtime; surfaces opt in, and any surface that lifts uses it rather than its own copy (INV15).

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 29dc337

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The owner-approved direction for INV14 and INV15 is clear, but three corrections are needed before this record can merge:

  • INV15 calls the trigger clone the transient copy that component-style-authoring INV7 allows, while INV7 allows only one copy during an exit, a presentation switch or a sheet handoff, made by one mechanism every layer uses; amend INV7 here or keep the real trigger.
  • The record still says it describes only the shipped runtime, yet no presentation hold, exit copy, runtime backdrop tint or lift module exists today, and Dialog, Drawer, BottomSheet, Lightbox, MobileNav, ToastViewport and Selector build their own; update that sentence and list these under Current gaps.
  • The runtime-owned backdrop tint and top-layer mounting still have component owners in the overlay-dismissal family's AV6, this record's boundary sentence and its Owning code; amend those lines or narrow INV14.

A new commit invalidates the exact-head approval, so an engineering owner (the author included) will need to comment /approve-spec <full head SHA> again on the corrected head.

[Automated review]

Comment on lines +466 to +467
trigger is the one transient copy `architecture:component-style-authoring`
INV7 allows: inert, hidden from assistive technology, and removed when the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

component-style-authoring INV7 allows only one transient copy "during an exit, a presentation switch, or a handoff between sheets", made "by one Core mechanism that every layer uses". INV14 already names the runtime's exit copy as that mechanism, and this module is separate from the runtime and used only by lifting surfaces. Amend INV7 in this pull request to admit the lift clone, or keep the real trigger; "the performance outcomes" that choose between them are not defined anywhere.

Comment on lines +454 to +456
`architecture:component-style-authoring` INV7 names), and the backdrop tint,
drawn with `::backdrop`. A component that needs one of these uses the
runtime's rather than building its own. Each component still decides when it

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The runtime now owns the backdrop tint and top-layer mounting, but the overlay-dismissal family's AV6 ("Members own animation, visual treatment, and theming anatomy"), this record's boundary sentence ("It does not own ... visual treatment") and its Owning code ("Dialog families own native modal/backdrop presentation"; "Drawer owns modal showModal() and non-modal showPopover() hosting") still give those to components. Except the runtime's tint and mounting there, or narrow this claim.

| INV8 | `useLayerDismissal.test.tsx`, `layerDismissalInvariants.test.tsx`, and `useFocusTrap.test.tsx` | Current top registered layer is skipped, two layers close, or a blocker leaks through |
| INV9 | Representative Dialog, ContextMenu, Tooltip/HoverCard, and BottomSheet source/tests | A current local channel silently changes ownership or policy |
| INV10 | `LayerProvider.tsx`, `LayerProvider.test.tsx`, `useToast.tsx`, `ToastViewport.test.tsx`, and `ToastViewport.modalHost.test.tsx` | Provider begins relocating ordinary layers, Toast fallback loses its current lifecycle, or a default provider renders differently from no provider |
| INV14, INV15 | Source boundary check over Core for top-layer hosts, exit copies, backdrop elements, lift, and rectangle-morph code outside the runtime and the lift module | A component building its own copy of a mechanism INV14 lists (top-layer mounting, close-request delivery, hold, exit copy, or backdrop element), or re-implementing the lift or the grow |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This failure signal is met by shipped code as soon as it merges: Dialog, Drawer, BottomSheet, Lightbox and MobileNav draw their own ::backdrop tint, Selector keeps its own presentation hold, and six surfaces call showModal() or showPopover() themselves. Meanwhile the record still says it describes only the shipped runtime and lists none of these under Current gaps.

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 25, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 0acc952

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec f7567a7

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The owner-approved direction for INV14 and INV15 is clear, and the INV14/INV15 gap now sits outside the AST-003 paragraph, but one wording correction is needed before this record can merge. INV14 ends "The public surface stays useLayer and LayerProvider", while the published @astryxdesign/core/Layer entry also exports useLayerDismissal (the close-request delivery INV14 calls internal), LayerDepthProvider, useTouchTrigger, isActionTrigger, LayerContext, useLayerContext and layerAnimations, which the public-component-api record counts as public contract; saying instead that these mechanisms add no public API and the published exports are unchanged fixes it. Two optional clarifications would help implementers: where the shared rectangle morph lives for surfaces that grow without lifting (AST-094 FR31), and which mechanism makes the lift clone. A new commit invalidates the exact-head approval, so an engineering owner (the author included) will need to comment /approve-spec <full head SHA> again on the corrected head.

[Automated review]

Comment thread docs/architecture/layer-runtime.md Outdated
Comment on lines +461 to +462
`family:overlay-dismissal` AV3, AV4, AV6; `spec:AST-003` FR10). The public
surface stays `useLayer` and `LayerProvider`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The published @astryxdesign/core/Layer entry (packages/core/package.json, ./Layer) also exports useLayerDismissal (the close-request delivery this invariant calls internal), LayerDepthProvider, useTouchTrigger, isActionTrigger, LayerContext, useLayerContext and layerAnimations, and the public-component-api record counts published subpaths and exported hooks as public contract. Saying "These mechanisms add no public API; the runtime's published exports are unchanged" keeps the claim true.

… shared mechanics, and one presentation module owns lift and grow (INV14, INV15)
…t copy; non-lifting layers do not use the module
…ft clone is INV7's one inert copy; drop the not-yet-shipped ContextMenu gap
…nts keep dismissal, hosting, and presentation policy
…visual treatment to components; the record lists INV14/INV15's current gaps
… list; the lift's choice is compositor-only motion
@cixzhang
cixzhang force-pushed the spec/layer-runtime-shared-presentation branch from f7567a7 to d795cd8 Compare October 26, 2026 00:20
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec d795cd8

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This looks ready to merge: INV14 and INV15 carry engineering-owner approval for this exact head, and they conflict with none of the current records they touch. One optional clarification would help implementers: say where the shared rectangle morph lives, because Popover and DropdownMenu must grow from their trigger without lifting, while INV15 keeps layers that do not lift out of the lift module. If you add it, an engineering owner needs to comment /approve-spec <full head SHA> again on the new head.

[Automated review]

internal module separate from the layer runtime: the lift from the trigger,
using the real trigger or a clone, whichever keeps the motion on the
compositor (`spec:AST-094` FR5);
the grow from the trigger's rectangle through the shared rectangle morph

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional clarification: say where the shared rectangle morph lives. Popover must grow from its trigger without lifting (Popover FR9, from the touch-presentations spec's FR31), INV15 keeps layers that do not lift out of this module, and the Verification row fails rectangle-morph code outside the runtime and the lift module, so the runtime is its only conforming home.

@cixzhang
cixzhang merged commit 66e257a into main Oct 26, 2026
68 checks passed
@github-actions
github-actions Bot deleted the spec/layer-runtime-shared-presentation branch October 26, 2026 06:31

This branch was successfully deployed

1 active deployment
Preview — d795cd89 Deployed Oct 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant