Repository navigation
spec(core): BottomSheet stacks by open order and replaces on a same-update switch; BottomSheetSwitcher is deprecated - #8010
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report
The exact-head preview is not available yet. Generated by PR Enrichment workflow | View current CI run |
There was a problem hiding this comment.
This spec change is clearly scoped, but approval waits on an engineering owner's decision: the head adds new normative requirements to three current records (BottomSheet DEC-2 with FR5–FR12; BottomSheetSwitcher DEC-3, DEP-0015 and FR10; two layer-runtime paragraphs), and no owner approval is recorded for this head yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner. Independently, four record fixes are needed:
- DEP-0015 and DEC-3 promise the switcher's handoff for every flow, but FR10 falls back to push-and-pop motion when a control outside the closing sheet acts; state that condition in the replacement and migration, or widen FR10.
- DEP-0015's
deprecatedstate and the lifecycle text describe@deprecateddeclarations and migrated docs and templates that main does not have yet; add implementation-gap notes, or record the state asproposedas other unshipped DEP records do. - FR11's gap note says a non-modal sheet returns no focus, but main returns focus to
finalFocusRefon every close; correct the note and list the narrowing under Compatibility. - The
[feat]patch class cites AST-017 FR28, which governs deprecation paths; FR7 and FR25 are the clauses that decide the tag and patch admissibility.
[Automated review]
| | package | `@astryxdesign/core` | | ||
| | surface | `BottomSheetSwitcher`, `BottomSheetSwitcherProps`, and the `sheetId` prop of `BottomSheet` | | ||
| | old contract | A multi-step flow is a `BottomSheetSwitcher` whose `activeSheet` names one nested `BottomSheet` by `sheetId`; stepping runs the handoff of FR2–FR3 and FR8 | | ||
| | replacement | One standalone `BottomSheet` per step with `isOpen={step === id}`, driven by one state value; closing the open step and opening the next in the same update runs the same handoff (`component:BottomSheet` FR10), for modal and `hasScrim={false}` flows alike | |
There was a problem hiding this comment.
FR10 keeps "the ordinary push and pop motion" when "a control outside the closing sheet" acts, so this replacement does not run the same handoff for a flow stepped from the page (a non-modal flow leaves the page interactive) or programmatically after focus left the sheet. The switcher hands off on any activeSheet change. Please state the condition here, in the migration and in DEC-3, or widen FR10.
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | Maintained Astryx surfaces (CLI block templates, stories, Lab) use the replacement; the switcher's own stories remain as its labelled reference | | ||
| | transition | no prior transition | | ||
| | state | `deprecated`: supported, documented with its replacement, unchanged at runtime; this is the documentation step of the component deprecation procedure | |
There was a problem hiding this comment.
On main no declaration carries @deprecated, BottomSheetSwitcher.doc.mjs still recommends the switcher, and the CLI block templates and Lab's MobilePowerSearch and MobileTokenizer use it. Every other unmet row gets an implementation gap note; this state (and the lifecycle text and the DEP-0015 verification row) needs one too, or proposed until the implementation lands, as Selector DEP-0011 and Banner DEP-0007 do.
| | FR8 | A sheet rendered in another sheet's content is presented only while its host is open: it leaves the top layer with its host and is presented again, on top, if its own `isOpen` is still true when the host reopens. Its `isOpen` is never written by the host. Implementation gap: on current `main` a nested sheet stays open in the top layer with no visible box when its host closes from code. | DEC-2 | | ||
| | FR9 | Closing the top sheet returns focus into the sheet it reveals: to the element that opened the closing sheet when that element is inside the revealed sheet, else to the revealed sheet's panel; an explicit `finalFocusRef` outside the revealed sheet yields to its panel in the same way. The last sheet of a stack keeps the standalone rule: `finalFocusRef`, else the opener. Closing every sheet of a stack in one update returns focus once, to the bottom sheet's `finalFocusRef` or opener, after the last dialog has closed. A close driven by the stack itself never moves focus. Implementation gap: on current `main` closing several sheets in one update leaves focus on `body`. | DEC-2 | | ||
| | FR10 | When exactly one standalone sheet closes and exactly one opens in the same update, both of one document and one modality, the closing sheet was the top sheet of its stack with the opening sheet set aside, neither dialog contains the other, and the control last pressed (or, with no fresh press, the element focused when the update began) is inside the closing sheet's dialog or is none, the opening sheet replaces the closing one: it enters above the retained sheet; a retained sheet whose top edge is above the entering sheet's resting top edge moves down to meet it at the same time; the retained sheet fades only after both motions complete; it is `inert` and `aria-hidden` from the first frame and takes no dismissal request; the one scrim stays with the retained sheet until it leaves, when the entering sheet's backdrop takes over with no transition; focus moves into the entering sheet; and the entering sheet adopts the retained sheet's focus-return target, so closing the flow returns focus to its first opener. A close and an open that fail any condition — different updates, a covered sheet closing, several sheets changing, a sheet closing with its host, or a control outside the closing sheet acting — keep the ordinary push and pop motion and their own focus return. Implementation gap: on current `main` a same-update close and open is a pop beneath a push. | DEC-2 | | ||
| | FR11 | A non-modal sheet (`hasScrim={false}`) is presented in the browser's top layer as a manual popover with no backdrop, no scroll lock, and no focus trap, and carries no page-level `z-index`; where the Popover API is absent it is presented with `show()` in page stacking order as a reduced fallback. Non-modal sheets never stack, recede, or own a scrim, and never pair with a modal sheet, but two non-modal sheets replace each other under FR10 with no backdrop. On a lone open the sheet takes focus only through a `data-autofocus` descendant; when it is replacing the sheet the person was in, it takes focus; when it closes while focus is inside it, focus returns to its `finalFocusRef`, else its opener. Implementation gap: on current `main` a non-modal sheet is presented with `show()` and a page-level `z-index: 1000`, and returns no focus on close. | DEC-2; `spec:AST-027/FR8`; `architecture:layer-runtime` | |
There was a problem hiding this comment.
"returns no focus on close" is not what main does: BottomSheet.tsx moves focus to finalFocusRef after every exit, modal or not, and captures an opener only for modal sheets. FR11's "while focus is inside" rule narrows that released behavior, so the Compatibility bullet should list it.
| page-level band remains on that path. | ||
| - `architecture:layer-runtime` owns the toast viewport's re-entry above a | ||
| non-modal sheet, through the same private registry Dialog outlets use. | ||
| - `spec:AST-017` FR28 classes stacking, the replace handoff, and the non-modal |
There was a problem hiding this comment.
AST-017 FR28 governs when a deprecation path is required; the [feat] tag comes from FR7 and patch admissibility from FR25 (additive, implementation-equivalent, FR3 default, FR5 victim-free restoration, or FR53 accessibility fix). Please cite those and say which FR25 category covers the changed two-sheet behavior; system_specs lists FR28 too.
4d2c408 to
c31d498
Compare
There was a problem hiding this comment.
This spec change is clearly scoped, but approval waits on an engineering owner's decision: it adds new normative requirements to three current records (BottomSheet DEC-2 with FR5–FR12, AR3–AR4 and PR1–PR2; BottomSheetSwitcher DEC-3, DEP-0015 and FR10; two layer-runtime paragraphs), and no owner approval is recorded for this head yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Independently, three record fixes are needed:
- The layer-runtime implementation gap note says a toast raised while a non-modal sheet is open paints beneath it on
main, but there the sheet usesshow()atz-index: 1000while the toast viewport is a top-layer popover, so toasts already paint above it; the ordering problem appears only once FR11 moves the sheet into the top layer. - FR11 and AR4 say a lone non-modal sheet takes no focus on open and note only the host as unmet, but in Chromium the released sheet's native
show()moves focus to its panel (main's jsdom test stubsshow()), so the rows need that gap, a real-browser failure signal and a Compatibility line. - FR10 also replaces when the actor "is none", while DEP-0015 and DEC-3 say code after focus has left the step always gets push and pop, so the three places should use one wording.
[Automated review]
| rendered with `isTopLayer={false}` stays in place. Implementation gap: on | ||
| current `main` the registry carries only Dialog outlets, and a toast raised | ||
| while a non-modal sheet is open paints beneath it. |
There was a problem hiding this comment.
On main a non-modal sheet is not a top-layer surface: BottomSheet.tsx presents it with show() under zIndex: 1000, while ToastViewport defaults to popover="manual" and showPopover(), so toasts already paint above it. The ordering problem appears once FR11 moves the sheet into the top layer. Could the gap note say that the registry carries only Dialog outlets and a non-modal sheet is not yet a top-layer surface?
| | FR8 | A sheet rendered in another sheet's content is presented only while its host is open: it leaves the top layer with its host and is presented again, on top, if its own `isOpen` is still true when the host reopens. Its `isOpen` is never written by the host. Implementation gap: on current `main` a nested sheet stays open in the top layer with no visible box when its host closes from code. | DEC-2 | | ||
| | FR9 | Closing the top sheet returns focus into the sheet it reveals: to the element that opened the closing sheet when that element is inside the revealed sheet, else to the revealed sheet's panel; an explicit `finalFocusRef` outside the revealed sheet yields to its panel in the same way. The last sheet of a stack keeps the standalone rule: `finalFocusRef`, else the opener. Closing every sheet of a stack in one update returns focus once, to the bottom sheet's `finalFocusRef` or opener, after the last dialog has closed. A close driven by the stack itself never moves focus. Implementation gap: on current `main` closing several sheets in one update leaves focus on `body`. | DEC-2 | | ||
| | FR10 | When exactly one standalone sheet closes and exactly one opens in the same update, both of one document and one modality, the closing sheet was the top sheet of its stack with the opening sheet set aside, neither dialog contains the other, and the control last pressed (or, with no fresh press, the element focused when the update began) is inside the closing sheet's dialog or is none, the opening sheet replaces the closing one: it enters above the retained sheet; a retained sheet whose top edge is above the entering sheet's resting top edge moves down to meet it at the same time; the retained sheet fades only after both motions complete; it is `inert` and `aria-hidden` from the first frame and takes no dismissal request; the one scrim stays with the retained sheet until it leaves, when the entering sheet's backdrop takes over with no transition; focus moves into the entering sheet; and the entering sheet adopts the retained sheet's focus-return target, so closing the flow returns focus to its first opener. A close and an open that fail any condition — different updates, a covered sheet closing, several sheets changing, a sheet closing with its host, or a control outside the closing sheet acting — keep the ordinary push and pop motion and their own focus return. Implementation gap: on current `main` a same-update close and open is a pop beneath a push. | DEC-2 | | ||
| | FR11 | A non-modal sheet (`hasScrim={false}`) is presented in the browser's top layer as a manual popover with no backdrop, no scroll lock, and no focus trap, and carries no page-level `z-index`; where the Popover API is absent it is presented with `show()` in page stacking order as a reduced fallback. Non-modal sheets never stack, recede, or own a scrim, and never pair with a modal sheet, but two non-modal sheets replace each other under FR10 with no backdrop. On a lone open the sheet takes focus only through a `data-autofocus` descendant; when it is replacing the sheet the person was in, it takes focus. On close an explicit `finalFocusRef` is focused wherever focus is, and otherwise the element focused when the sheet was shown is focused again only while focus is still inside the sheet — the native dialog's own return rule, kept. Implementation gap: on current `main` a non-modal sheet is presented with `show()` and a page-level `z-index: 1000`; its focus return (`finalFocusRef` after every exit, the native return to the element focused at `show()` while focus is inside) already matches this row. | DEC-2; `spec:AST-027/FR8`; `architecture:layer-runtime` | |
There was a problem hiding this comment.
In Chromium the released non-modal sheet takes focus on open: the native show() runs the dialog focusing steps and moves focus to the panel (tabindex="-1"), and focusPanel does not undo it; the jsdom test that asserts no focus move stubs show(). So "takes focus only through a data-autofocus descendant" is unmet on main too. Please add that to this gap note and AR4's, add a real-browser failure signal to the FR10–FR11 verification row, and list the open-focus change in Compatibility.
| | package | `@astryxdesign/core` | | ||
| | surface | `BottomSheetSwitcher`, `BottomSheetSwitcherProps`, and the `sheetId` prop of `BottomSheet` | | ||
| | old contract | A multi-step flow is a `BottomSheetSwitcher` whose `activeSheet` names one nested `BottomSheet` by `sheetId`; stepping runs the handoff of FR2–FR3 and FR8 | | ||
| | replacement | One standalone `BottomSheet` per step with `isOpen={step === id}`, driven by one state value; closing the open step and opening the next in the same update, from a control inside the open step (or from code while focus is in it), runs the same handoff (`component:BottomSheet` FR10), for modal and `hasScrim={false}` flows alike. A step changed from a control outside the flow — a page control beside a non-modal flow, or code after focus has left the step — is a close and an open with their own push and pop motion, where the switcher ran its handoff on any `activeSheet` change | |
There was a problem hiding this comment.
FR10 also replaces when the actor "is none" (no fresh press and nothing focused). Here, in migration step (5) and in DEC-3, "code after focus has left the step" always gets push and pop; in a modal flow that focus can only fall to the body. Could the three places use one wording, or FR10 define "none"?
c31d498 to
b834c8c
Compare
There was a problem hiding this comment.
This spec change is clearly scoped, but approval waits on an engineering owner's decision: it adds new normative requirements to three current records (BottomSheet DEC-2 with FR5–FR12, AR3–AR4 and PR1–PR2; BottomSheetSwitcher DEC-3, DEP-0015 and FR10; two layer-runtime paragraphs), and no owner approval is recorded for this head yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. The earlier record fixes are in; two more are needed either way:
- FR11 and AR4 send a lone non-modal sheet's open focus to a
data-autofocusdescendant and FR11's gap note saysmainalready does, but onmainthe nativeshow()focuses the panel first andfocusPanelthen leaves focus there (the jsdom tests stubshow()), so the gap notes, AR4's wording and the FR10–FR11 verification row need that case. - DEP-0015's migration covers
activeSheet,onActiveSheetChange,sheetIdandhasScrimbut not the switcher'sonCancel, itsrefto the hosting dialog or its dialog props, which a standalone sheet does not offer or forwards to its panel; please map each one or say it has no equivalent.
[Automated review]
| | FR8 | A sheet rendered in another sheet's content is presented only while its host is open: it leaves the top layer with its host and is presented again, on top, if its own `isOpen` is still true when the host reopens. Its `isOpen` is never written by the host. Implementation gap: on current `main` a nested sheet stays open in the top layer with no visible box when its host closes from code. | DEC-2 | | ||
| | FR9 | Closing the top sheet returns focus into the sheet it reveals: to the element that opened the closing sheet when that element is inside the revealed sheet, else to the revealed sheet's panel; an explicit `finalFocusRef` outside the revealed sheet yields to its panel in the same way. The last sheet of a stack keeps the standalone rule: `finalFocusRef`, else the opener. Closing every sheet of a stack in one update returns focus once, to the bottom sheet's `finalFocusRef` or opener, after the last dialog has closed. A close driven by the stack itself never moves focus. Implementation gap: on current `main` closing several sheets in one update leaves focus on `body`. | DEC-2 | | ||
| | FR10 | When exactly one standalone sheet closes and exactly one opens in the same update, both of one document and one modality, the closing sheet was the top sheet of its stack with the opening sheet set aside, neither dialog contains the other, and the actor — the control last pressed, or, with no fresh press, the element focused when the update began — is inside the closing sheet's dialog or is none (no fresh press, and nothing or only the document body focused, as when code changes the step while focus has fallen to the body), the opening sheet replaces the closing one: it enters above the retained sheet; a retained sheet whose top edge is above the entering sheet's resting top edge moves down to meet it at the same time; the retained sheet fades only after both motions complete; it is `inert` and `aria-hidden` from the first frame and takes no dismissal request; the one scrim stays with the retained sheet until it leaves, when the entering sheet's backdrop takes over with no transition; focus moves into the entering sheet; and the entering sheet adopts the retained sheet's focus-return target, so closing the flow returns focus to its first opener. A close and an open that fail any condition — different updates, a covered sheet closing, several sheets changing, a sheet closing with its host, or a control outside the closing sheet acting — keep the ordinary push and pop motion and their own focus return. Implementation gap: on current `main` a same-update close and open is a pop beneath a push. | DEC-2 | | ||
| | FR11 | A non-modal sheet (`hasScrim={false}`) is presented in the browser's top layer as a manual popover with no backdrop, no scroll lock, and no focus trap, and carries no page-level `z-index`; where the Popover API is absent it is presented with `show()` in page stacking order as a reduced fallback. Non-modal sheets never stack, recede, or own a scrim, and never pair with a modal sheet, but two non-modal sheets replace each other under FR10 with no backdrop. On a lone open, focus moves to the panel when the browser's own focusing steps move it into the sheet (as the native `show()` does) and to a `data-autofocus` descendant when one is present; when the sheet is replacing the sheet the person was in, it takes focus. On close an explicit `finalFocusRef` is focused wherever focus is, and otherwise the element focused when the sheet was shown is focused again only while focus is still inside the sheet — the native dialog's own return rule, kept. Implementation gap: on current `main` a non-modal sheet is presented with `show()` and a page-level `z-index: 1000`; its focus on open (the native focusing steps land on the panel) and its focus return (`finalFocusRef` after every exit, the native return to the element focused at `show()` while focus is inside) already match this row. | DEC-2; `spec:AST-027/FR8`; `architecture:layer-runtime` | |
There was a problem hiding this comment.
On main a non-modal sheet with an <input data-autofocus> opens with the panel focused, not the field: the native show() focuses the panel (tabindex="-1") first, and focusPanel returns early because focus is already inside the panel. The jsdom test that expects the field stubs show(). So "already match this row" is not true for the data-autofocus clause; please add it to this gap note and AR4's, add a real-browser failure for it to the FR10–FR11 row, and list the change in Compatibility (or drop the clause).
| | replacement | One standalone `BottomSheet` per step with `isOpen={step === id}`, driven by one state value; closing the open step and opening the next in the same update, from a control inside the open step (or from code while focus is in it), runs the same handoff (`component:BottomSheet` FR10), for modal and `hasScrim={false}` flows alike. A step changed by a control outside the flow's sheets — a page control beside a non-modal flow, or code acting while focus is on a page control — is a close and an open with their own push and pop motion, where the switcher ran its handoff on any `activeSheet` change; code acting while focus is in the step or on the document body still runs the handoff (`component:BottomSheet` FR10's actor is inside the step, or none) | | ||
| | direct authority | DEC-3 here; `component:BottomSheet` DEC-2 and FR10 own the replacement's behavior. Owners: `cixzhang`, `imdreamrunner` | | ||
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff | |
There was a problem hiding this comment.
The migration maps activeSheet, onActiveSheetChange, sheetId and hasScrim, but BottomSheetSwitcherProps also has onCancel (whose preventDefault() keeps the flow open), a ref to the hosting <dialog>, id/data-testid and dialog props on every sheet dialog, and a consumer aria-label that outranks the step label. A standalone BottomSheet has no onCancel and sends its ref and props to the panel <div>. Could a step say where each goes, or that it has no equivalent?
b834c8c to
cad131f
Compare
There was a problem hiding this comment.
This spec change is clearly scoped, but approval waits on an engineering owner’s decision: it adds new normative requirements to three current records (BottomSheet DEC-2 with FR5–FR12, AR3–AR4 and PR1–PR2; BottomSheetSwitcher DEC-3, DEP-0015 and FR10; two layer-runtime paragraphs), and no owner approval is recorded for this head yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. The earlier record fixes are in; these are still needed either way:
- AR4 says a lone modal sheet focuses a
data-autofocusdescendant first, but onmainshowModal()focuses the panel andfocusPanelleaves focus there (the jsdom test stubsshowModal()), while Compatibility says a lone modal sheet behaves as released; add the gap note, a real-browser failure signal and a Compatibility line, or state the released panel focus. - DEP-0015 step (6) says
formkeeps Escape from closing a step, butformallows Escape and onlyrequiredblocks it. - DEP-0015 keeps Lab’s
MobilePowerSearchon the switcher after the deprecation patch, which AST-017 FR29c does not allow for maintained packages; migrate it withMobileTokenizeror settle the exception with AST-017’s owner. - DEP-0015’s
staterow names a “component deprecation procedure” that no current record defines; drop the clause or point at AST-017 FR28.
[Automated review]
| focusing steps enter the sheet, with no `data-autofocus` preference — while | ||
| a modal sheet, and a non-modal sheet entering as a replacement, focus a | ||
| `data-autofocus` descendant first (FR11). Implementation gap: on current `main` a pop returns focus to the |
There was a problem hiding this comment.
On main a modal sheet with an <input data-autofocus> opens with the panel focused, not the field: showModal() focuses the panel (tabindex="-1") and focusPanel returns early because focus is already inside it (the jsdom test stubs showModal()). The Compatibility bullet says a lone modal sheet behaves as released, so this clause needs a gap note, a real-browser failure signal and a Compatibility line, or should state the released panel focus.
| | replacement | One standalone `BottomSheet` per step with `isOpen={step === id}`, driven by one state value; closing the open step and opening the next in the same update, from a control inside the open step (or from code while focus is in it), runs the same handoff (`component:BottomSheet` FR10), for modal and `hasScrim={false}` flows alike. A step changed by a control outside the flow's sheets — a page control beside a non-modal flow, or code acting while focus is on a page control — is a close and an open with their own push and pop motion, where the switcher ran its handoff on any `activeSheet` change; code acting while focus is in the step or on the document body still runs the handoff (`component:BottomSheet` FR10's actor is inside the step, or none) | | ||
| | direct authority | DEC-3 here; `component:BottomSheet` DEC-2 and FR10 own the replacement's behavior. Owners: `cixzhang`, `imdreamrunner` | | ||
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes each step's `purpose` (`form` or `required` keeps Escape or every implicit dismissal from closing the step; there is no per-event veto) or a guarded `onOpenChange` that ignores `false` while the flow must stay open; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | |
There was a problem hiding this comment.
Step (6) says form keeps Escape from closing the step, but form allows Escape (switcher FR6; BottomSheet.tsx closes on Escape for every purpose but required). An onCancel that vetoes Escape maps to purpose="required" or a guarded onOpenChange.
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes each step's `purpose` (`form` or `required` keeps Escape or every implicit dismissal from closing the step; there is no per-event veto) or a guarded `onOpenChange` that ignores `false` while the flow must stay open; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | Maintained Astryx surfaces use the replacement: the CLI block templates, the stories, and Lab's `MobileTokenizer`; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it (`component:BottomSheetSwitcher` keeps working for it). The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
AST-017 FR29c says maintained Astryx packages migrate in the deprecation patch, and this row treats Lab’s MobileTokenizer as maintained. Could MobilePowerSearch migrate in the same patch, or the exception be settled with AST-017’s owner?
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | Maintained Astryx surfaces use the replacement: the CLI block templates, the stories, and Lab's `MobileTokenizer`; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it (`component:BottomSheetSwitcher` keeps working for it). The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | | ||
| | transition | no prior transition | | ||
| | state | `deprecated`: supported, documented with its replacement, unchanged at runtime; this is the documentation step of the component deprecation procedure. Implementation gap: on current `main` the declarations, consumer docs, CLI block templates, and Lab consumers do not yet carry or use the replacement, so the state is not yet realized | |
There was a problem hiding this comment.
No current record defines a component deprecation procedure or its documentation step (nor the marked alias in open decision). Could this point at AST-017 FR28 instead, or drop the clause?
cad131f to
2459e5a
Compare
There was a problem hiding this comment.
This spec change is clearly scoped, but approval waits on an engineering owner's decision: it adds new normative requirements to three current records (BottomSheet DEC-2 with FR5–FR12, AR3–AR4 and PR1–PR2; BottomSheetSwitcher DEC-3, DEP-0015 and FR10; two layer-runtime paragraphs), and no owner approval is recorded for this head yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. The earlier record fixes are in; one more is needed either way:
- DEP-0015 lets Lab's
MobilePowerSearchkeep the switcher after the deprecation patch, but AST-017 FR29c says maintained Astryx packages migrate in that patch, and the FR2 and non-goal it cites concern Lab's own surfaces rather than its use of a deprecated Core component; migrate it withMobileTokenizer, or have AST-017's owner record the exception.
[Automated review]
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes each step's `purpose` or a guarded `onOpenChange`: an `onCancel` that vetoes Escape and the platform close maps to `purpose="required"` (`form` blocks only the scrim tap and the swipe, and Escape closes every purpose but `required`), and a veto that depends on state maps to an `onOpenChange` that ignores `false` while the flow must stay open; there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | The maintained published surfaces migrate in the deprecation patch (`spec:AST-017` FR29c): the CLI block templates and the stories. `@astryxdesign/lab` is a private, unpublished package, outside `spec:AST-017`'s compatibility promise (its non-goals and FR2), so its migration is optional under FR29c: Lab's `MobileTokenizer` migrates with the patch; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it, and the switcher keeps working for it. The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
This row lets Lab's MobilePowerSearch keep the switcher after the deprecation patch, but AST-017 FR29c says "Maintained Astryx packages and authoring surfaces migrate in the deprecation patch", with no published-only qualifier. FR2 and the non-goal cited here concern Lab's own surfaces, not its use of a deprecated Core component, and this row already migrates Lab's MobileTokenizer in the same patch. Could MobilePowerSearch migrate in that patch too, or AST-017's owner record the exception there so this row can cite it (the Intent's gap note would follow)?
2459e5a to
806c75a
Compare
There was a problem hiding this comment.
This spec change is clearly scoped, but approval waits on an engineering owner's decision: it adds new normative requirements to three current records (BottomSheet DEC-2 with FR5–FR12, AR3–AR4 and PR1–PR2; BottomSheetSwitcher DEC-3, DEP-0015 and FR10; two layer-runtime paragraphs), and no owner approval is recorded for this head yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Three record fixes are needed either way:
- DEP-0015 still lets Lab's
MobilePowerSearchkeep the switcher after the deprecation patch, but AST-017 FR29c says maintained Astryx packages migrate in that patch and approving these records does not amend AST-017; migrate it withMobileTokenizer, or record the exception in AST-017 and cite it, and drop the open reading from the row. - The layer-runtime record now says a covered sheet reports itself absent to the dismissal stack and is not a toast outlet, but on
mainit stays present and registered; add gap notes for both sentences, ascomponent:BottomSheetFR7 has. - DEP-0015 step (6) maps an Escape-vetoing
onCanceltopurpose="required"without saying that also makes the step analertdialog; name it, or point that case at the guardedonOpenChange.
[Automated review]
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes each step's `purpose` or a guarded `onOpenChange`: an `onCancel` that vetoes Escape and the platform close maps to `purpose="required"` (`form` blocks only the scrim tap and the swipe, and Escape closes every purpose but `required`), and a veto that depends on state maps to an `onOpenChange` that ignores `false` while the flow must stay open; there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | The maintained published surfaces migrate in the deprecation patch (`spec:AST-017` FR29c): the CLI block templates and the stories. `@astryxdesign/lab` is a private, unpublished package; `spec:AST-017` lists private packages among its non-goals and FR2 puts them outside the published compatibility promise, which this record reads as FR29c's "maintained Astryx packages" not reaching Lab, so Lab's migration is optional under it: Lab's `MobileTokenizer` migrates with the patch; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it, and the switcher keeps working for it. Whether `spec:AST-017` is read that way is its owner's to settle. The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
This row keeps Lab's MobilePowerSearch on the switcher after the deprecation patch on this record's own reading of AST-017 FR29c, but FR29c says "Maintained Astryx packages and authoring surfaces migrate in the deprecation patch" with no published-only qualifier, and approving this record does not amend AST-017. Could MobilePowerSearch migrate with MobileTokenizer, or the exception be recorded in AST-017 and cited here? Either way the row should not leave the reading open.
| requests. A BottomSheet covered by, or being replaced by, another sheet of its | ||
| stack reports itself absent to the shared stack while that lasts | ||
| (`component:BottomSheet` FR7), so the order among sheets is the sheet stack's | ||
| and the shared stack stays one registry. Other family members still use local Escape handling as listed in |
There was a problem hiding this comment.
On main a covered sheet still reports itself present (isPresent is the dialog's open state in BottomSheet.tsx) and the shared stack ranks depth first; likewise every open scrim sheet stays registered as a toast outlet, so the toast paragraph's "a covered sheet is not an outlet" is unmet too. component:BottomSheet FR7 carries this gap; could this record carry gap notes for both sentences?
| | replacement | One standalone `BottomSheet` per step with `isOpen={step === id}`, driven by one state value; closing the open step and opening the next in the same update, from a control inside the open step (or from code while focus is in it), runs the same handoff (`component:BottomSheet` FR10), for modal and `hasScrim={false}` flows alike. A step changed by a control outside the flow's sheets — a page control beside a non-modal flow, or code acting while focus is on a page control — is a close and an open with their own push and pop motion, where the switcher ran its handoff on any `activeSheet` change; code acting while focus is in the step or on the document body still runs the handoff (`component:BottomSheet` FR10's actor is inside the step, or none) | | ||
| | direct authority | DEC-3 here; `component:BottomSheet` DEC-2 and FR10 own the replacement's behavior. Owners: `cixzhang`, `imdreamrunner` | | ||
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes each step's `purpose` or a guarded `onOpenChange`: an `onCancel` that vetoes Escape and the platform close maps to `purpose="required"` (`form` blocks only the scrim tap and the swipe, and Escape closes every purpose but `required`), and a veto that depends on state maps to an `onOpenChange` that ignores `false` while the flow must stay open; there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | |
There was a problem hiding this comment.
Step (6) maps an Escape-vetoing onCancel to purpose="required", which on a standalone sheet also sets role="alertdialog" and blocks the scrim tap and the swipe, while a switcher step with info or form keeps its dialog role. Could the step name the role change, or point an Escape-only veto at the guarded onOpenChange?
806c75a to
ecc4d13
Compare
There was a problem hiding this comment.
This spec change is clearly scoped, but approval waits on an engineering owner's decision: it adds new normative requirements to three current records (BottomSheet DEC-2 with FR5–FR12, AR3–AR4 and PR1–PR2; BottomSheetSwitcher DEC-3, DEP-0015 and FR10; two layer-runtime paragraphs), and no owner approval is recorded for this head yet. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. The covered-sheet gap notes and the alertdialog naming are in; three record fixes are still needed either way:
- DEP-0015 still lets Lab's
MobilePowerSearchkeep the switcher after the deprecation patch, but AST-017 FR29c says maintained Astryx packages migrate in that patch and approving these records does not amend AST-017; migrate it withMobileTokenizer, or record the exception in AST-017 and cite it, and drop the open reading from the row. - The layer-runtime toast paragraph now says the viewport is above every top-layer surface shown before a toast is raised, but only modal outlets and the no-scrim sheet announce themselves, and a non-modal Drawer (
showPopover()) still covers toasts onmain; scope the sentence to those surfaces, or add a gap note for the others. - DEP-0015 step (6) says the guarded
onOpenChangekeeps the step's scrim-tap and swipe behaviour, but it ignores those closes too while it holds, since there is no per-event veto; please say so.
[Automated review]
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes a guarded `onOpenChange` on each step — one that ignores `false` while the flow must stay open — which keeps the step's role and its scrim-tap and swipe behavior and so is the primary mapping for a veto of Escape and the platform close, conditional or not; `purpose="required"` is the mapping only when the step should also block the scrim tap and the swipe and be exposed as an `alertdialog`, which that purpose does on a standalone sheet (`form` blocks only the scrim tap and the swipe, and Escape closes every purpose but `required`); there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | The maintained published surfaces migrate in the deprecation patch (`spec:AST-017` FR29c): the CLI block templates and the stories. `@astryxdesign/lab` is a private, unpublished package; `spec:AST-017` lists private packages among its non-goals and FR2 puts them outside the published compatibility promise, which this record reads as FR29c's "maintained Astryx packages" not reaching Lab, so Lab's migration is optional under it: Lab's `MobileTokenizer` migrates with the patch; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it, and the switcher keeps working for it. Whether `spec:AST-017` is read that way is its owner's to settle. The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
This row still keeps Lab's MobilePowerSearch on the switcher after the deprecation patch on this record's own reading of AST-017 FR29c, but FR29c says "Maintained Astryx packages and authoring surfaces migrate in the deprecation patch" with no published-only qualifier, and approving this record does not amend AST-017. Could MobilePowerSearch migrate with MobileTokenizer, or the exception be recorded in AST-017 and cited here, with the open reading dropped from the row?
| | replacement | One standalone `BottomSheet` per step with `isOpen={step === id}`, driven by one state value; closing the open step and opening the next in the same update, from a control inside the open step (or from code while focus is in it), runs the same handoff (`component:BottomSheet` FR10), for modal and `hasScrim={false}` flows alike. A step changed by a control outside the flow's sheets — a page control beside a non-modal flow, or code acting while focus is on a page control — is a close and an open with their own push and pop motion, where the switcher ran its handoff on any `activeSheet` change; code acting while focus is in the step or on the document body still runs the handoff (`component:BottomSheet` FR10's actor is inside the step, or none) | | ||
| | direct authority | DEC-3 here; `component:BottomSheet` DEC-2 and FR10 own the replacement's behavior. Owners: `cixzhang`, `imdreamrunner` | | ||
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes a guarded `onOpenChange` on each step — one that ignores `false` while the flow must stay open — which keeps the step's role and its scrim-tap and swipe behavior and so is the primary mapping for a veto of Escape and the platform close, conditional or not; `purpose="required"` is the mapping only when the step should also block the scrim tap and the swipe and be exposed as an `alertdialog`, which that purpose does on a standalone sheet (`form` blocks only the scrim tap and the swipe, and Escape closes every purpose but `required`); there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | |
There was a problem hiding this comment.
Step (6) says the guarded onOpenChange keeps the step's scrim-tap and swipe behaviour, but on a standalone sheet Escape, the scrim tap and the swipe all arrive as onOpenChange(false), so the guard holds the step open against all three while it applies; the switcher's onCancel vetoed only the cancel request. Could the step say so, so an Escape-only veto is not read as having an exact equivalent?
| handoff survive. The rule is one of order, not of modality: the viewport is | ||
| above every top-layer surface that was shown before a toast is raised. A |
There was a problem hiding this comment.
This rule covers every top-layer surface, but only modal outlets and the no-scrim sheet announce themselves through the registry. A non-modal Drawer is a popover="manual" surface shown with showPopover(), and on main the viewport re-enters the top layer only when the outlet changes, so a toast raised while that Drawer is open paints beneath it. Could the sentence be scoped to outlets and non-modal sheets, or gain a gap note naming the surfaces that do not announce?
ecc4d13 to
fbff436
Compare
There was a problem hiding this comment.
Approval of this spec change waits on an engineering owner's decision on the new requirements it adds to component:BottomSheet (DEC-2, FR5–FR15, AR3–AR5, PR1–PR2) and component:BottomSheetSwitcher (DEC-3, DEP-0015, FR10 and the amended Handoff state); an engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. One record fix is needed either way: DEP-0015's downstream row keeps Lab's MobilePowerSearch on the switcher after the deprecation patch, but spec:AST-017 FR29c says maintained Astryx packages migrate in that patch, so migrate it with MobileTokenizer or record a Lab exception in AST-017 first and cite it here.
[Automated review]
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes a guarded `onOpenChange` on each step — one that ignores `false` while the flow must stay open — which keeps the step's dialog role; a standalone sheet reports Escape, the platform close, the scrim tap, and the swipe all as `onOpenChange(false)` with no reason, so the guard holds the step open against all of them while it applies, where the switcher's `onCancel` vetoed only the cancel request; a veto of Escape alone has no equivalent, and the only per-path lever is `purpose` (`form` keeps Escape and blocks the scrim tap and the swipe; `required` blocks all three and exposes the step as an `alertdialog`); there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | The maintained published surfaces migrate in the deprecation patch (`spec:AST-017` FR29c): the CLI block templates and the stories. `@astryxdesign/lab` is marked `private` and is published to npm only under the `@canary` dist-tag, never as a stable `latest` release; `spec:AST-017` calls it "the private canary-only Lab package" (FR27), lists private packages among its non-goals, puts them outside the published compatibility promise (FR2), and says canary-only surfaces carry no stable compatibility promise, which this record reads as FR29c's "maintained Astryx packages" not reaching Lab, so Lab's migration is optional under it: Lab's `MobileTokenizer` migrates with the patch; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it, and the switcher keeps working for it. Whether `spec:AST-017` is read that way is its owner's to settle. The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
This row keeps Lab's MobilePowerSearch on the switcher after the deprecation patch, but spec:AST-017 FR29c says "Maintained Astryx packages and authoring surfaces migrate in the deprecation patch." FR2 and FR27 bound what Lab promises its consumers, not Lab's own use of a deprecated Core surface, and this row migrates Lab's MobileTokenizer in the same patch. Approving this record does not amend AST-017, so either migrate MobilePowerSearch with MobileTokenizer (and drop the Intent's "is migrated by its own change" sentence), or record the Lab exception in AST-017 FR29c first and cite it here.
d71af0e to
36202e5
Compare
There was a problem hiding this comment.
Approval of this spec change waits on an engineering owner's decision on the new requirements it adds to component:BottomSheet (DEC-2, FR5–FR15, AR3–AR5, PR1–PR2) and component:BottomSheetSwitcher (DEC-3, DEP-0015, FR10 and the amended Handoff state); an engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. One record fix is needed either way: DEP-0015's downstream row keeps Lab's MobilePowerSearch on the switcher after the deprecation patch, but spec:AST-017 FR29c says maintained Astryx packages migrate in that patch, so migrate it with MobileTokenizer or record a Lab exception in AST-017 first and cite it here.
[Automated review]
36202e5 to
c9279c0
Compare
There was a problem hiding this comment.
Approval of this spec change waits on an engineering owner's decision on the new requirements it adds to component:BottomSheet (DEC-2, FR5–FR15, AR3–AR5, PR1–PR2) and component:BottomSheetSwitcher (DEC-3, DEP-0015, FR10 and the amended Handoff state). An engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Three record fixes are needed either way:
- DEP-0015's
downstreamrow keeps Lab'sMobilePowerSearchon the switcher after the deprecation patch, butspec:AST-017FR29c says maintained Astryx packages migrate in that patch. Migrate it withMobileTokenizer, or record a Lab exception in AST-017 first and cite it here. - The DEP-0015 verification row checks only the switcher's own docs, but
BottomSheet.doc.mjsstill says to useBottomSheetSwitcherfor multi-step flows. Add BottomSheet's consumer doc to the row and to the Intent's gap note. - The switcher's Public concepts table still lists its concepts,
sheetIdincluded, asreleased. Mark themdeprecatedto match the Intent and DEP-0015.
[Automated review]
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes a guarded `onOpenChange` on each step — one that ignores `false` while the flow must stay open — which keeps the step's dialog role; a standalone sheet reports Escape, the platform close, the scrim tap, and the swipe all as `onOpenChange(false)` with no reason, so the guard holds the step open against all of them while it applies, where the switcher's `onCancel` vetoed only the cancel request; a veto of Escape alone has no equivalent, and the only per-path lever is `purpose` (`form` keeps Escape and blocks the scrim tap and the swipe; `required` blocks all three and exposes the step as an `alertdialog`); there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | The maintained published surfaces migrate in the deprecation patch (`spec:AST-017` FR29c): the CLI block templates and the stories. `@astryxdesign/lab` is marked `private` and is published to npm only under the `@canary` dist-tag, never as a stable `latest` release; `spec:AST-017` calls it "the private canary-only Lab package" (FR27), lists private packages among its non-goals, puts them outside the published compatibility promise (FR2), and says canary-only surfaces carry no stable compatibility promise, which this record reads as FR29c's "maintained Astryx packages" not reaching Lab, so Lab's migration is optional under it: Lab's `MobileTokenizer` migrates with the patch; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it, and the switcher keeps working for it. Whether `spec:AST-017` is read that way is its owner's to settle. The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
spec:AST-017 FR29c ends "Maintained Astryx packages and authoring surfaces migrate in the deprecation patch." FR2 and FR27 limit what Lab promises its own consumers, not Lab's use of a deprecated Core surface, and this row migrates Lab's MobileTokenizer in the same patch. Approving this record does not amend AST-017. Either migrate MobilePowerSearch with MobileTokenizer (and drop the Intent's "is migrated by its own change" sentence), or record a Lab exception in AST-017 FR29c first and cite it here.
| | FR9 | `BottomSheetSwitcher.test.tsx`; export checks | ref across open/handoff/close/unmount, callback-ref cleanup, `id` once, DOM/ARIA/data/events, class, style, xstyle | A supported input is dropped or duplicated, an object ref is null while a sheet is mounted or skips a cleanup, or an input overrides component-owned semantics accidentally. | `audit:BottomSheetSwitcher/api` | | ||
| | Theming anatomy | `scripts/check-knowledge.mjs`; theming tests | dialog, delegated sheet panel, scrim | A non-painting host gains a target, a delegated panel loses its owner, or the scrim gap disappears without review. | `audit:BottomSheetSwitcher/theming` | | ||
| | FR10 | `component:BottomSheet`'s stacking suite (the switcher-as-a-level cases); real-browser dim and recede sampling of a flow stacked over, and under, a standalone sheet | switcher base covered by a standalone sheet, switcher on top of a standalone sheet, a sheet in a step's content, flow closing over a lower sheet | Two backdrops paint, the covered level does not recede, a covered flow takes Escape, or focus lands on `body` when the flow closes over a sheet. Implementation gap: the suite does not exist on current `main`. | `audit:BottomSheetSwitcher/behavior` | | ||
| | DEP-0015 | Declaration and consumer-doc inspection; `@deprecated` metadata on the three declarations; CLI template and story sources | the switcher's declarations, docs, templates, and stories | A declaration lacks `@deprecated`, a maintained template or story teaches the switcher, or a production runtime branch, log, or output changes with the deprecation. Implementation gap: on current `main` every declaration lacks `@deprecated` and the templates teach the switcher. | `audit:BottomSheetSwitcher/api` | |
There was a problem hiding this comment.
This row checks only the switcher's own declarations, docs, templates and stories. packages/core/src/BottomSheet/BottomSheet.doc.mjs still says "use BottomSheetSwitcher for multi-step flows" (lines 175 and 327) and tells multi-step flows to configure hasScrim on the switcher (line 168), and spec:AST-017 FR29b requires maintained docs to teach only the replacement. Could the row's states and failure list, and the Intent's gap note, name BottomSheet's consumer doc too?
| prop or changing a default. Where the shipped switcher does not yet meet a | ||
| requirement, the row carries a present-tense implementation gap note. The | ||
| modality, identifier, and theming questions below stay open. | ||
| prop or changing a default. Its public lifecycle state is `deprecated` |
There was a problem hiding this comment.
The lifecycle here is deprecated, but the Public concepts table (lines 138–142) still gives every switcher concept, sheetId included, the Stability released. The template's Stability column holds the lifecycle state, so could those cells read deprecated (DEP-0015)?
c9279c0 to
61a9a3b
Compare
There was a problem hiding this comment.
Approval of this spec change waits on an engineering owner's decision on the new requirements it adds to component:BottomSheet (DEC-2, FR5–FR15, AR3–AR5, PR1–PR2) and component:BottomSheetSwitcher (DEC-3, DEP-0015, FR10, the deprecated Stability cells and the amended Handoff state). An engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. One record fix is needed either way:
- DEP-0015's
downstreamrow and the Intent keep Lab'sMobilePowerSearchon the switcher after the deprecation patch, butspec:AST-017FR29c says maintained Astryx packages migrate in that patch: migrate it withMobileTokenizer, or record a Lab exception in AST-017 first and cite it here.
The earlier notes on BottomSheet's consumer doc and on the Public concepts Stability column are resolved at this head.
[Automated review]
61a9a3b to
037b273
Compare
There was a problem hiding this comment.
Approval of this spec change waits on an engineering owner's decision on the new requirements it adds to component:BottomSheet (DEC-2, FR5–FR14, AR3–AR5, PR1–PR2) and component:BottomSheetSwitcher (DEC-3, DEP-0015, FR10, the deprecated lifecycle, and the Stacked and amended Handoff states). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Either way, one record fix is needed, and one wording fix is suggested:
- DEP-0015's
downstreamrow and the Intent keep Lab'sMobilePowerSearchon the switcher after the deprecation patch.spec:AST-017FR29c says maintained Astryx packages migrate in that patch, so either migrate it withMobileTokenizeror record a Lab exception in AST-017 first and cite it here. - BottomSheet's Compatibility section says a lone modal sheet behaves as released, but FR15 changes its reduced-motion entrance and exit. State that exception, as the switcher record does for its handoff.
[Automated review]
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes a guarded `onOpenChange` on each step — one that ignores `false` while the flow must stay open — which keeps the step's dialog role; a standalone sheet reports Escape, the platform close, the scrim tap, and the swipe all as `onOpenChange(false)` with no reason, so the guard holds the step open against all of them while it applies, where the switcher's `onCancel` vetoed only the cancel request; a veto of Escape alone has no equivalent, and the only per-path lever is `purpose` (`form` keeps Escape and blocks the scrim tap and the swipe; `required` blocks all three and exposes the step as an `alertdialog`); there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | The maintained published surfaces migrate in the deprecation patch (`spec:AST-017` FR29c): the CLI block templates and the stories. `@astryxdesign/lab` is marked `private` and is published to npm only under the `@canary` dist-tag, never as a stable `latest` release; `spec:AST-017` calls it "the private canary-only Lab package" (FR27), lists private packages among its non-goals, puts them outside the published compatibility promise (FR2), and says canary-only surfaces carry no stable compatibility promise, which this record reads as FR29c's "maintained Astryx packages" not reaching Lab, so Lab's migration is optional under it: Lab's `MobileTokenizer` migrates with the patch; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it, and the switcher keeps working for it. Whether `spec:AST-017` is read that way is its owner's to settle. The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
This row keeps MobilePowerSearch on the switcher after the deprecation patch, but spec:AST-017 FR29c says "Maintained Astryx packages and authoring surfaces migrate in the deprecation patch." FR2, FR26 and the non-goal it cites are about Lab's own surfaces, not about Lab using a deprecated Core one. To fix it, either:
- migrate
MobilePowerSearchwithMobileTokenizerin the patch, and drop this clause and the Intent sentence at line 56; or - record the Lab exception in AST-017 first, then cite it here.
| implementation-equivalent for a sheet on its own: a modal sheet presented on | ||
| its own behaves as released (focus on open wherever the engine's |
There was a problem hiding this comment.
Under reduced motion, a lone modal sheet does not behave as released: FR15 moves its entrance and exit from shortened transforms to design:reduced-motion's cross-fade or immediate change. Worth stating that exception here, as the switcher record does for its handoff: a visual-only motion change under spec:AST-017 FR3.
037b273 to
275d6f3
Compare
There was a problem hiding this comment.
Approval of this spec change waits on an engineering owner’s decision on the new requirements it adds to component:BottomSheet (DEC-2, FR5–FR15, AR3–AR5, PR1–PR2) and component:BottomSheetSwitcher (DEC-3, DEP-0015, FR10 and the deprecated lifecycle). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. One record fix is needed either way: DEP-0015 and the switcher’s Intent keep Lab’s MobilePowerSearch on the switcher after the deprecation patch, while spec:AST-017 FR29c says maintained Astryx packages migrate in that patch, so migrate it with MobileTokenizer or record a Lab exception in AST-017 first and cite it. The description also still says all three records carry approved_by: imdreamrunner, which neither changed record does, so it is worth refreshing before an owner reads it.
[Automated review]
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes a guarded `onOpenChange` on each step — one that ignores `false` while the flow must stay open — which keeps the step's dialog role; a standalone sheet reports Escape, the platform close, the scrim tap, and the swipe all as `onOpenChange(false)` with no reason, so the guard holds the step open against all of them while it applies, where the switcher's `onCancel` vetoed only the cancel request; a veto of Escape alone has no equivalent, and the only per-path lever is `purpose` (`form` keeps Escape and blocks the scrim tap and the swipe; `required` blocks all three and exposes the step as an `alertdialog`); there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | The maintained published surfaces migrate in the deprecation patch (`spec:AST-017` FR29c): the CLI block templates and the stories. `@astryxdesign/lab` is marked `private` and is published to npm only under the `@canary` dist-tag, never as a stable `latest` release; `spec:AST-017` calls it "the private canary-only Lab package" (FR27), lists private packages among its non-goals, puts them outside the published compatibility promise (FR2), and says canary-only surfaces carry no stable compatibility promise, which this record reads as FR29c's "maintained Astryx packages" not reaching Lab, so Lab's migration is optional under it: Lab's `MobileTokenizer` migrates with the patch; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it, and the switcher keeps working for it. Whether `spec:AST-017` is read that way is its owner's to settle. The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
This row keeps Lab’s MobilePowerSearch on the switcher after the deprecation patch, but spec:AST-017 FR29c says “Maintained Astryx packages and authoring surfaces migrate in the deprecation patch”, with no private or canary exception; the FR2, FR26 and non-goal clauses cited here cover Lab’s own surfaces, and this row migrates Lab’s MobileTokenizer anyway. Approving this record does not amend AST-017, so either migrate MobilePowerSearch with MobileTokenizer and drop the clause (and the Intent sentence at lines 56–57), or record the Lab exception in AST-017 first and cite it.
275d6f3 to
0e7caf8
Compare
There was a problem hiding this comment.
Approval of this spec change waits on an engineering owner's decision on the new requirements it adds to component:BottomSheet (DEC-2, FR5–FR15, AR3–AR5, PR1–PR2) and component:BottomSheetSwitcher (DEC-3, DEP-0015, FR10 and the deprecated lifecycle). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. One record fix is needed either way, because approving these records does not amend AST-017:
- DEP-0015's
downstreamrow and the switcher's Intent keep Lab'sMobilePowerSearchon the switcher after the deprecation patch, whilespec:AST-017FR29c says maintained Astryx packages migrate in that patch, so migrate it withMobileTokenizeror record a Lab exception in AST-017 first and cite it.
[Automated review]
…pdate switch; BottomSheetSwitcher is deprecated component:BottomSheet DEC-2 and FR5–FR12 record the stack of standalone modal sheets per document (open order, tree-order ties, one visible scrim, covered sheets receded and inert, dismissal to the top sheet only, nested sheets that leave and return with their host without their state being written, focus return on a pop and on a close of the whole stack), the same-update replace handoff between standalone sheets, non-modal sheets hosted in the top layer as manual popovers, and a BottomSheetSwitcher flow as one level of the stack. AR3–AR4 record the inertness and focus contract; PR1–PR2 the one registry per page and the one-backdrop cost; the verification map names the stacking and replace suites. component:BottomSheetSwitcher stays current with every requirement in force; DEC-3 and DEP-0015 record its lifecycle as deprecated (supported, maintained, docs point to standalone step sheets, no runtime warning, removal only in a future declared minor), and FR10 records how a flow takes part in the stack. Its per-step dialogs (DEC-1, DEC-2) are unchanged. architecture:layer-runtime records the toast viewport's re-entry above a non-modal top-layer surface and the sheet stack's standing-down from the shared dismissal stack. Rows current main does not meet carry present-tense implementation gap notes.
0e7caf8 to
37ddc4a
Compare
There was a problem hiding this comment.
Approval of this spec change waits on an engineering owner's decision on the new requirements it adds to component:BottomSheet (DEC-2, FR5–FR15, AR3–AR5, PR1–PR2) and component:BottomSheetSwitcher (DEC-3, DEP-0015, FR10 and the deprecated lifecycle); an engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Two record fixes are needed either way, because approving these records amends neither spec:AST-017 nor design:reduced-motion:
- DEP-0015's
downstreamrow and the switcher's Intent keep Lab'sMobilePowerSearchon the switcher after the deprecation patch, whilespec:AST-017FR29c says maintained Astryx packages migrate in that patch, so migrate it withMobileTokenizeror record a Lab exception in AST-017 first and cite it. - FR15, the Reduced motion state and the stacking verification row make the covered sheet jump to its receded place and back on a coarse pointer, while
design:reduced-motionDR2 requires every such transition there to change opacity only, so cross-fade it within 200 ms or have a design owner record the exception there.
[Automated review]
| | FR12 | A modal `BottomSheetSwitcher` flow is one level of the stack: the dialog of its active step is the flow's level, keeps the flow's rank across the flow's own handoffs, recedes and is blocked when covered, yields its backdrop to the stack's scrim owner, stands down from dismissal while covered, and returns focus by FR9. A standalone sheet rendered in a step's content stacks above the flow and leaves with the step (FR8). A switcher step is never paired under FR10. Implementation gap: on current `main` a switcher flow and a standalone sheet open together are unrelated dialogs. | DEC-2; `component:BottomSheetSwitcher/DEC-1` | | ||
| | FR13 | Every open scrim sheet supplies its dialog as a passive global outlet for app-global surfaces such as toasts (`spec:AST-003` FR18: each active native modal, a covered sheet included), and a modal `BottomSheetSwitcher` flow supplies its active step's dialog — the one its host dialog resolves to, following each handoff; the retained step's dialog, inert and fading, never hosts. Among sheets, the latest outlet — the one a global host renders into — follows the stack's order, not the order the sheets were supplied in: the top sheet is the latest, so a toast lands in the top sheet and never in a covered, inert one, and when the top sheet closes the sheet it reveals is the latest again and takes the host back. A sheet is raised only among sheet outlets: an outlet supplied later by another modal (a `Dialog` opened from a sheet) stays above every sheet until it closes. A sheet, or a flow, stops supplying as soon as it starts closing, and a native `close` event that fires while its dialog is open again (the stack re-showing it, or a reopen during its exit) does not take its outlet away. Implementation gap: on current `main` the latest outlet is the one supplied last, so a sheet re-shown or revealed by its stack is not it, and a switcher flow supplies no outlet, so a toast raised from a step lands behind the flow. | DEC-2; `spec:AST-003/FR16`, `/FR18`; `architecture:layer-runtime`; `component:BottomSheetSwitcher/FR10` | | ||
| | FR14 | When a non-modal sheet enters the top layer, a top-layer toast viewport shown before it leaves and re-enters the top layer in place, keeping focus inside it, so toasts paint above the sheet. Implementation gap: on current `main` a non-modal sheet is not a top-layer surface (`show()` under a page-level `z-index`, beneath the popover viewport), so the toast viewport's re-entry above a non-modal sheet has nothing to answer. | DEC-2; `architecture:layer-runtime` | | ||
| | FR15 | Under `prefers-reduced-motion: reduce` the recede, the return from receded, a push's entrance, a pop's exit and the replace handoff — and a `BottomSheetSwitcher` step's, which shares the sheet panel's motion — follow `design:reduced-motion`, keyed on its queries (`prefers-reduced-motion: reduce`, with `pointer: coarse` for the cross-fade): no transform motion runs (DR1) — a covered sheet appears in its receded place and size the frame the push lands and returns the same way, the entering sheet appears in its final place, the retained sheet does not align down; on a coarse pointer the entering sheet fades in and the leaving or retained sheet fades out where it is, opacity only, in 200 ms or less (DR2); a dismissal the release of a touch drag started (a swipe past the dismiss line) fades out in place the same way on every pointer (DR2's drag-release clause); on any other pointer every other change is immediate (DR3); the handoff's steps run in the released order, so the retained sheet is hidden and its dialog closed once its fade ends. The constant page dim through a push, a pop and a handoff (FR6) holds as it does with motion. Implementation gap: on current `main` there is no recede or handoff between standalone sheets, and the shipped sheet's own entrance and exit under reduced motion run their transforms shortened on every pointer and do not cross-fade on a coarse pointer. | DEC-2; `design:reduced-motion` | |
There was a problem hiding this comment.
Under reduced motion on a coarse pointer, design:reduced-motion DR2 says each DR1 transition “MUST change opacity only and MUST finish in 200 ms or less”, and its coarse-pointer verification row fails “a change with no cross-fade”. This row has the covered sheet appear “in its receded place and size the frame the push lands” and return the same way on every pointer, so on a touch screen the recede and return are one-frame jumps of scale and offset. Cross-fade the covered sheet into its receded place and back on a coarse pointer (keeping the fine-pointer change immediate), and match the Reduced motion state and the stacking verification row — or have a design owner record the exception in design:reduced-motion first.
| | warning | `@deprecated` on the declarations; consumer docs, templates, stories, and release notes name the replacement. No runtime warning (`spec:AST-017` FR29) | | ||
| | migration | Ordered and testable, per flow: (1) replace `activeSheet` / `onActiveSheetChange` with one `step` value; (2) give each step `isOpen={step === 'id'}` and `onOpenChange={isOpen => !isOpen && setStep(current => (current === 'id' ? null : current))}` in place of `sheetId="id"`; (3) remove the wrapper, moving its `hasScrim` onto every step; (4) keep the step controls as they are (`setStep('next')`); (5) a flow stepped by a control outside its sheets (a page control beside a non-modal flow, or code acting while focus is on a page control) gets push and pop motion in place of the handoff — move such a control into the step, or accept the motion; code acting while focus is in the step or on the body keeps the handoff; (6) the switcher's other props: `onCancel` becomes a guarded `onOpenChange` on each step — one that ignores `false` while the flow must stay open — which keeps the step's dialog role; a standalone sheet reports Escape, the platform close, the scrim tap, and the swipe all as `onOpenChange(false)` with no reason, so the guard holds the step open against all of them while it applies, where the switcher's `onCancel` vetoed only the cancel request; a veto of Escape alone has no equivalent, and the only per-path lever is `purpose` (`form` keeps Escape and blocks the scrim tap and the swipe; `required` blocks all three and exposes the step as an `alertdialog`); there is no per-event veto; a consumer `aria-label` / `aria-labelledby` becomes each step's `label`, which names its dialog; `ref`, `id`, `data-testid`, `className`, `style`, `xstyle`, and the other dialog props have no dialog-level equivalent — a standalone sheet's `ref` and base props reach its panel `<div>` and its dialog is implementation — so a test or an anchor that needs the dialog queries the panel's `closest('dialog')`, and dialog-level styling is dropped | | ||
| | codemod | None; the rewrite moves state the caller owns and is short enough to read | | ||
| | downstream | The maintained published surfaces migrate in the deprecation patch (`spec:AST-017` FR29c): the CLI block templates and the stories. `@astryxdesign/lab` is marked `private` and is published to npm only under the `@canary` dist-tag, never as a stable `latest` release; `spec:AST-017` calls it "the private canary-only Lab package" (FR27), lists private packages among its non-goals, puts them outside the published compatibility promise (FR2), and says canary-only surfaces carry no stable compatibility promise, which this record reads as FR29c's "maintained Astryx packages" not reaching Lab, so Lab's migration is optional under it: Lab's `MobileTokenizer` migrates with the patch; Lab's `MobilePowerSearch` keeps the switcher until its own change migrates it, and the switcher keeps working for it. Whether `spec:AST-017` is read that way is its owner's to settle. The switcher's own stories remain as its labelled reference. Implementation gap: on current `main` the templates and `MobileTokenizer` use the switcher | |
There was a problem hiding this comment.
spec:AST-017 FR29c reads “Maintained Astryx packages and authoring surfaces migrate in the deprecation patch”, with no published or stable qualifier, and approving this record does not amend it. The clauses cited here (FR2, FR26, FR27, the non-goal) limit the compatibility promise of Lab's own surfaces, not Lab's use of a deprecated Core surface, and this row already treats Lab as maintained by migrating MobileTokenizer. Migrate MobilePowerSearch in the deprecation patch too, or record the Lab exception in AST-017 first and cite it here.
|
/approve-spec 37ddc4a |
Exact decision needed
How two or more bottom sheets presented in one document relate to one another, and what happens to
BottomSheetSwitcheronce standalone sheets can do a flow's handoff on their own. Three claims, none settled by a current record today:BottomSheetopened while another is open stacks on it: ordered by open order, one visible scrim, the covered sheet receded and blocked, dismissal reaching only the top sheet, a sheet nested in another's content leaving and returning with its host, focus returned into the revealed sheet on a pop and once to the first opener on a close of the whole stack.hasScrim={false}) are hosted in the top layer as manual popovers, never stack, and replace each other the same way.BottomSheetSwitcherkeeps working, deprecated: supported and maintained, every requirement of its record in force, docs and templates pointing to standalone step sheets, removal only in a future declared minor.The
architecture:layer-runtimefacts the model depends on (the sheet stack standing down from the shared dismissal stack, which sheet is the latest toast outlet, and the toast re-entry above a non-modal sheet) are recorded here ascomponent:BottomSheetrows with gap notes —layer-runtimedescribes the runtime shipped onmain, by its own scope sentence, so it is not edited by this record; the implementation adds those sentences to it when it ships them, as a records change in the implementation pull request (the precedent the per-step switcher change set).Existing authority searched
component:BottomSheet(current): FR1–FR4, AR1–AR2, DEC-1, OQ1. Owns the native dialog every sheet renders and standalone presentation; says nothing about two open sheets, and its Public concepts section says no public concept is introduced. Nothing settles order, scrim ownership, or focus between sheets.component:BottomSheetSwitcher(current): FR1–FR9, AV1–AV2, ORD1–ORD2, PR1–PR2, AR1–AR3, DEC-1 (each step owns its native dialog), DEC-2 (the host dialog's identity is implementation), OQ1–OQ3. Owns a flow's own handoff. Its ownership boundary said an ordered visible sheet stack is "proposed separately by openBottomSheetStackwork and not part of this component" — a pointer to a proposal, not a claim.architecture:layer-runtime(current): owns the modal outlet registry and the toast viewport's promotion into an open Dialog; silent on a non-modal top-layer surface that is not an outlet.spec:AST-027(current) FR8 asks for top-layer hosting of non-modal surfaces and lists the sheets'show()+ page-levelz-indexpath as a conformance gap; FR4 forbids a new page band.spec:AST-017(current) FR26 (lifecycle states), FR28 (a patch prepares a break with a deprecation;DEP-*records describe and never gate), FR29/FR29a/FR29b (no runtime behavior; declarations and docs carry the route).spec:AST-002DEC-5 requires current authority for behavior changes.family:overlay-dismissalFR2–FR3 own Escape order.component:SelectorDEP-0011 / CLN-0020 is the DEP record shape followed here;component:DrawerDEC-4 and the Tokenizer DEC-3 amendment are the shape for amending a current component record in place with present-tense "Implementation gap" notes.main. No other open record pull request touches these three files.Canonical owner. The existing current component records are amended rather than a new
AST-044record created. The facts are component-local: which sheet is on top, which backdrop paints, where focus goes, and the switcher's lifecycle arecomponent:BottomSheetandcomponent:BottomSheetSwitcherclaims, andarchitecture:knowledge-contractsINV2 puts aggregate observable component behavior in the component record. A system record would exist only to compare three candidate models, which is research (R2) and a pull-request verdict (INV10), not a durable fact boundary. The one shared-system fact (toast re-entry) goes to the architecture record that already owns the outlet registry.Decision
Owner direction, recorded here so the record itself can stay in the present tense:
The owner (
imdreamrunner) approves this record change; the/approve-specis posted at the final head. The two records keep the approval linesmaincarries today —component:BottomSheetapproved_by: cixzhang,approved_at: 2026-10-24(from #8304) andcomponent:BottomSheetSwitcherapproved_by: imdreamrunner,approved_at: 2026-10-24(from #8272) — until/approve-specrewrites them; the new DEC-2 decider line readsimdreamrunner, 2026-10-23, the Switcher's DEC-3 and DEP-0015 linesimdreamrunner, 2026-10-20andimdreamrunner, 2026-10-23.component:BottomSheet— DEC-2, FR5–FR12, AR3–AR4, PR1–PR2inertandaria-hidden. Exactly one backdrop is visible — the bottom open sheet's — and a change of scrim owner lands with no transition.onOpenChangeandpurpose; covered and replaced sheets stand down fromfamily:overlay-dismissalwhile covered.isOpenis still true when the host reopens. ItsisOpenis never written by the host.finalFocusRefoutside the revealed sheet yields to its panel the same way). The last sheet keeps the standalone rule (finalFocusRef, else opener). Closing every sheet in one update returns focus once, to the bottom sheet's target, after the last dialog has closed. A close driven by the stack never moves focus.inert/aria-hiddenfrom the first frame, the one scrim stays with the retained sheet until it leaves and the entering backdrop takes over with no transition, focus moves into the entering sheet, and the entering sheet adopts the retained sheet's focus-return target. Anything failing a condition is a push and a pop with their own focus return.show()beneath that modal with no page-levelz-index(the band is gone on both paths; AST-027 FR4), a modal on its way out counting as gone; once no such modal is open the sheet enters the top layer as a manual popover with the toast viewport re-entering above it (FR14), so page content never paints over an interactive non-modal sheet (AST-027 FR8) — no AST-027 gap remains. Rendered inside the modal's content it enters the top layer at once. The FR10–FR11 verification row carries these states (while open, once closed with positioned page content, opened in the update that closes a modal sheet, inside the modal) and its failure list now carries the paints-above / stays-beneath signals before the gap note, so they read as failure signals rather than facts aboutmain.show()/showModal()land on the panel (first focusable area incl. atabindex="-1"container), Firefox 151's on the first focusable control (adata-autofocusfield only when it comes first); WebKit unmeasured (could not be launched on the Linux node). The Compatibility class, AR4, FR11 and the FR10–FR11 failure list now say "wherever the engine's native focusing steps land" with those results, keep it as released behaviour, and FR11 says the manual-popover host lands where that engine'sshow()would have. The FR10–FR11 verification row names a per-engine lone-open probe (Chromium and Firefox at least) against bareshow()/showModal().main) and theDEP-0015verification row namecomponent:BottomSheet's consumer doc beside the switcher's own docs, templates and stories; the switcher's Public concepts table marks every concept (sheetIdincluded)deprecated (DEP-0015), the Stability spellingcomponent:TabListandcomponent:Listuse for their deprecated concepts.main's minimal-DOM change (spec: an open layer may render up to 5 more elements under touch, with no new anatomy (AST-017 DEC-29) #8439): the record keepsmain's element-jobs passage (architecture:component-style-authoringINV7,spec:AST-017FR60/DEC-29) and its "Element jobs" verification row beside PR1–PR2 and this PR's verification rows.main's AST-099 change (spec(AST-099): one shared on-screen keyboard module owns measurement and keyboard riding #8304): the record keepsmain's approval lines (cixzhang, 2026-10-24), itsspec:AST-099entry and ownership line, alongside this PR's system-spec additions.main's reduced-motion failure signal back ("under reduced motion, a handoff that moves a sheet, or on a coarse pointer does not cross-fade"), and its Handoff state says the retained step paints the one scrim only when the flow owns it — transparent while a lower sheet owns the stack's scrim (FR10, BottomSheet FR6).design:reduced-motionlanded onmain(spec(design): under reduced motion, touch cross-fades and other pointers change at once #8272) during review, and the Switcher record there now says a handoff follows it (DR2 cross-fade on touch, DR3 at once otherwise) with a gap note. This record's FR15 defers to that design record, keyed on its queries — no transform motion (DR1): the covered sheet appears in its receded place at once, the entering sheet in its final place, no alignment travel; cross-fade on a coarse pointer (DR2), immediate otherwise (DR3) — and names the switcher's steps as sharing the sheet panel's motion; its gap note describesmainonly (shortened transforms, no cross-fade). The Switcher record keepsmain's FR3 text and gap note, and FR10 says a flow's steps move as FR15 says. The implementation PR implements DR2/DR3 (maintainer decision, owner can redirect), so both gap notes close with it.architecture:knowledge-contractsINV10): the FR10–FR11 verification row names the per-engine lone-open focus probe in Chromium, Firefox and WebKit and the recording of focus/blur events on the first control (none); the status — Chromium and Firefox measured, WebKit unmeasured because Playwright's headless WebKit does not launch on the Linux evidence host — is stated here, not in the row. FR11 also says focus arrives whereshow()would have put it without passing through any control (no control receives a focus or a blur on a lone open), and the failure list names a transient focus or blur on a control.spec:AST-086FR14 delivers a message pending in a closing sheet through the sheet revealed beneath it): "the stack adds no announcement of its own to a push or a pop", with content-owned messages delivered by their owners' rules; the verification check reads "the stack renders no live region of its own". FR12 says a modal switcher flow, matching the switcher's FR10; FR15's basis is DEC-2 alone (spec:AST-012dropped fromsystem_specs); FR11 and AR4 no longer say "unmeasured here" — the verification row carries WebKit's status instead.prefers-reduced-motion: reduceno motion plays and nothing cross-fades in its place: the recede and return land in their end state the frame they apply, the entering step is at rest from its first frame, the retained step's alignment and fade each land instantly, the handoff's steps still run in the released order so it settles within a few frames with no intermediate values painted, the dim stays constant (measured in Chromium with reduced motion emulated — the retained dialog closes at ~116 ms); AR5 — a push or pop adds no announcement beyond the focus move, a replace is perceived as focus entering the new step'sdata-autofocusfield or panel with its dialog name announced, the retained step hidden from the first frame (citesspec:AST-020). Representative state "Reduced motion" added; the stacking verification row names a reduced-motion emulated run and the no-live-region / focus-entry checks. The implementation already behaves this way (every motion collapses to0.01sunder the query; no live region), so no code change follows.dialog, the top or entering sheet, with its name, while covered and replaced sheets are hidden — with the matching failure signals; the implementation's Chromium harness carries it.close.architecture:knowledge-contractsINV18): PR1 now reads "Sheets rendered by every copy of the package loaded on a page, and by every React root on it, form one stack per document … No public API reaches the ordering"; FR14 and AR4 no longer say "registry" (FR14: when a non-modal sheet enters the top layer the toast viewport re-enters above it; AR4: a replacement's focus is placed after the native focusing steps); DEC-2's rationale says the same in its own words. The Ownership bullet reads "the stack only tells it which sheet can take a close request (FR7)" instead of "which sheets are on screen".hasScrim={false}is a manual popover in the top layer, no backdrop, no scroll lock, no focus trap, no page-levelz-index;show()in page order as the reduced fallback without the Popover API. Never stacks or pairs with a modal sheet; two non-modal sheets replace under FR10. Focus: a lone open takes focus only throughdata-autofocus; a replace takes focus; on closefinalFocusRefis focused wherever focus is, and otherwise the element focused when the sheet was shown is focused again only while focus is still inside the sheet — the native dialog's own return rule, which the released sheet already has (finalFocusRefafter every exit from the script, the opener from the nativeclose()). Focus on open is stated truthfully: a sheet opened on its own, modal or not, is focused where the native host's focusing steps land — the panel — and adata-autofocusdescendant is not focused over it. Verified in Chromium on a static build ofmainand on the implementation head with two probe stories (ahasAutoFocusTextInput in a no-scrim sheet and in a modal sheet): the panel is focused, the field is not, identically on both builds for both modalities. The one deliberate exception, stated in AR4 with its reason, is a sheet entering as a replacement (FR10): the registry places focus itself because the native host may have left it in the replaced sheet, and there adata-autofocusdescendant is focused first. The FR10–FR11 verification row names both as real-browser failure signals; Compatibility lists the kept panel focus for the modal and the non-modal sheet. The row's gap note says only the host changes; the FR10–FR11 verification row gains a real-browser failure signal for it, and Compatibility lists the open-focus behaviour among the kept contract.inert+aria-hidden; a covered sheet containing the top sheet is left alone. AR4 focus follows the person (open, replace, pop, last close, close-all). PR1 one private registry per page shared by every package copy, one stack per document, no public API. PR2 one visible backdrop whatever the depth.spec:AST-017FR25 for a patch; whether this ships as a patch under FR25 or as a declared minor under FR31 is the owner's call — the argument for each is laid out in the implementation pull request's "Release tier" paragraph, and the records' Compatibility sections state the author's reading.[feat]tag comes fromspec:AST-017FR7 (a compatible capability); patch admissibility from FR25 as additive (no released contract described two presented sheets, so the stack gives behavior to a state no contract covered) and implementation-equivalent for a sheet on its own (a modal sheet on its own behaves as released; a non-modal sheet on its own keeps its documented contract — no scrim, no scroll lock, page interactive, dismissal bypurpose,finalFocusReffocused after every close, the element focused at show time focused again while focus is inside — while its host changes fromshow()+ page-levelz-indexto the top layer with none). Two modal sheets open at once stack instead of presenting as unrelated dialogs that each dim the page.system_specscitesspec:AST-017/FR7and/FR25; FR28 is cited only where it applies, the switcher's deprecation path.component:BottomSheetSwitcher— stays current, deprecated; DEC-3, DEP-0015, FR10currentand every existing requirement stays in force: DEC-1/DEC-2 per-step dialogs, FR1–FR9, AV1–AV2, ORD1–ORD2, PR1–PR2, AR1–AR3, the theming anatomy. No row is archived, loosened, or marked as no longer guaranteed. The per-step dialogs are what the stack registers: the active step's dialog is the flow's level (FR12 above, FR10 here).spec:AST-017FR26), with the gap notes a not-yet-shipped state needs: the Intent's lifecycle statement, the Compatibility lifecycle bullet, DEP-0015'sstateanddownstreamrows and its verification row each carry a present-tense "Implementation gap" note (onmainno declaration carries@deprecated, the switcher docs recommend the switcher, the CLI block templates and Lab'sMobileTokenizeruse it). Thedownstreamrow records the author's reading ofspec:AST-017FR29c, citing the clauses, not a ruling. Corrected facts:@astryxdesign/labis markedprivateand is published to npm, but only under the@canarydist-tag, never as a stablelatestrelease (private: trueis npm's guard against an accidental stable publish;publishConfig.canaryOnly). Re-checked against AST-017 with those facts: AST-017 itself calls Lab "the private canary-only Lab package" (FR27, and its Related-authorities section), lists private packages among its non-goals, puts them outside the published compatibility promise (FR2), and says "canary-only surfaces … carry no stable compatibility promise" (Browser/Support). So the reading — FR29c's "maintained Astryx packages" does not reach Lab, and Lab's migration is optional under it — still holds with the accurate description, and the row now states it that way. The maintained published surfaces (CLI block templates, stories) migrate in the deprecation patch;MobileTokenizermigrates with the patch;MobilePowerSearchkeeps the switcher until its own change migrates it, and the switcher keeps working for it. The row says plainly that whether AST-017 is read that way is its owner's to settle.component:BottomSheetDEC-2 — behavior of the stack, not a change to the switcher's own contract.component:BottomSheetFR10 defines its actor and "none": the control last pressed, or with no fresh press the element focused when the update began, is inside the closing sheet's dialog or is none (no fresh press, and nothing or only the document body focused, as when code changes the step while focus has fallen to the body). DEP-0015'sreplacementandmigrationrows (step 5) and DEC-3 use the same terms: a step changed by a control outside the flow's sheets — a page control beside a non-modal flow, or code acting while focus is on a page control — is a close and an open with the sheets' own motion; code acting while focus is in the step or on the body keeps the handoff. FR10 is not widened: the actor condition is what tells a step flow from two unrelated sheets toggled by one page control.onCancel→ a guardedonOpenChangeon each step (ignoresfalsewhile the flow must stay open), which keeps the step's dialog role. Stated plainly: a standalone sheet reports Escape, the platform close, the scrim tap and the swipe all asonOpenChange(false)with no reason, so the guard holds the step open against all of them while it applies, where the switcher'sonCancelvetoed only the cancel request; a veto of Escape alone has no equivalent, and the only per-path lever ispurpose(formkeeps Escape, blocks scrim tap and swipe;requiredblocks all three and exposes analertdialog); there is no per-event veto; a consumeraria-label/aria-labelledby→ each step'slabel;ref,id,data-testid,className,style,xstyleand the other dialog props have no dialog-level equivalent — a standalone sheet'srefand base props reach its panel<div>and its dialog is implementation — so a test or anchor that needs the dialog queries the panel'sclosest('dialog'), and dialog-level styling is dropped.BottomSheetStackwork" pointer is replaced by the owner (component:BottomSheetDEC-2).spec:AST-027gap; the deprecation leaves it to be closed by removal rather than by re-hosting.architecture:layer-runtimeNot edited. The record opens with "This record describes the layer runtime shipped on current
main", so it cannot carry rulesmaindoes not meet. The three facts that were there in earlier revisions are nowcomponent:BottomSheetrows, where the sheet behaviour is owned: FR7 (a covered or replaced sheet reports itself absent to the sharedfamily:overlay-dismissalstack, so the shared stack stays one registry), FR13 (every open scrim sheet supplies a toast outlet perspec:AST-003FR18, a covered sheet included, and a modalBottomSheetSwitcherflow supplies its active step's dialog, following each handoff — the retained step, inert and fading, never hosts; among sheets the latest outlet follows the stack's order; a sheet is raised only among sheet outlets, never above aDialogsupplied later; a stale nativeclosefrom a re-show does not take an outlet away; its gap note adds that onmaina switcher flow supplies no outlet, so a toast raised from a step lands behind the flow), and FR14 (a non-modal sheet announces its top-layer entry and the toast viewport re-enters above it). Each carries a gap note (PR2 now reads: one visible backdrop — the scrim owner's, the bottom open sheet's under FR6 — and every other backdrop in the stack transparent, not removed, consistent with Switcher FR10); a new verification row (FR13–FR14) names the toast-hosting suite and the real-browser hit-tests, with states for a modal flow alone, through a handoff and its close, a flow over a sheet and a sheet over a flow, and a non-modal flow.component:BottomSheetSwitcherFR10 says the flow's host dialog is an outlet like a standalone scrim sheet's, and its gap note says that onmaina flow supplies none (no step calls into the outlet registry;layer-runtimelists a scrim switcher among the modals toasts stay behind), so a toast from a step lands behind the flow. The Switcher's Compatibility statement qualifies "a flow on its own behaves as released" with that one fix. The implementation pull request adds the corresponding sentences tolayer-runtimeas the shipped behaviour, as the per-step switcher change did, with that record's approval metadata left atmain's values until an engineering owner's/approve-specis recorded there.Implementation gap notes
Every requirement
maindoes not meet carries a present-tense "Implementation gap" note, which the implementation pull request deletes:component:BottomSheetFR5, FR6, FR7, FR8, FR9, FR10, FR11 (host only: the released focus return already matches the row), FR12, AR3, AR4, and the two verification rows;component:BottomSheetSwitcher's Intent lifecycle statement, Compatibility lifecycle bullet, DEP-0015stateanddownstreamrows, FR10, and the FR10 and DEP-0015 verification rows; the new FR13 and FR14 and their verification row (the toast-outlet order and the non-modal re-entry, moved here fromlayer-runtime). One note outlives the implementation:MobilePowerSearchstill using the switcher (Intent anddownstream), until its own change lands.Rejected alternatives
Each is one
Rejected:line inside the decision that rejects it.activeSheets), the model the AST-044 draft and its implementation describe. Changes the switcher's public contract, requires every level of a flow to live under one switcher, and cannot order a sheet opened from elsewhere (aSelector's sheet from inside a step, a second widget). The owner's direction is C+, which deprecates the switcher instead.<BottomSheetStack>provider or shared dialog that composes levels. A React context cannot reach a second root, a second copy of the package, or a widget mounted outside it; module-level singletons already own document-wide facts in this repo (Layer/layerStack.ts,Layer/modalOutlet.ts). The only scoping a provider adds is a "same level" boundary, which FR10's actor condition supplies without a wrapper. The owner also preferred C+'s motion.stacked/replacesopt-in prop: a lone sheet is unaffected and one state value already says what a flow means.z-indexon a non-modal sheet to order a replace:spec:AST-027forbids a new band.[breaking]); a development-mode render warning (FR29 projects a component deprecation through declarations and docs).Claim scope and non-goals
The records govern the order, scrim ownership, blocking, dismissal routing, nested-sheet lifecycle, replace conditions and handoff, focus return, non-modal hosting, toast re-entry, the switcher's participation as a level, and the switcher's lifecycle. They do not decide the recede geometry's exact values or the handoff curve (allowed variation under theme motion tokens), any new public prop (none is introduced), the switcher's open questions OQ1–OQ3, whether
Dialog/Drawer/Popoveropened from a sheet stack (they keep their own hosting and are not levels), the release that removes the switcher, or the rename-versus-remove choice for that minor.Evidence and implementation relationship
feat/core-bottom-sheet-open-orderimplements FR5–FR12, AR3–AR4, the switcher's FR10, and the toast re-entry; addsBottomSheetStacking.test.tsxandBottomSheetReplace.test.tsxtoverified_by; deletes the gap notes; and carries the three Core[feat]patch Changesets and one CLI Changeset. Its description holds the real-browser evidence (Chromium and WebKit per-frame parity against the switcher's handoff, real Safari runs through the manual harness split out to chore(internal): manual real-Safari check for BottomSheet stacking, replace and non-modal hosting #8297, Close-all focus traces, toast order, ARIA trees).DEP-0011record; the Tokenizer DEC-3 amendment of a current record with gap notes; the BottomSheet/BottomSheetSwitcher promotion that set the per-step dialog contract this record builds on.Writing rubric results
BottomSheetStackwork") is removed and replaced by the owning record. The owner's direction and the candidate comparison live in this description, not in the records.Rejected:entries and DEC-3 three.#+ digits,/pull/,/issues/in the three records); the superseded pull requests and the deprecation-procedure pull request are named only in this description.open decision(rename or remove in the future minor) is genuinely unanswered and names both options, as the DEP shape asks; BottomSheet OQ1 and the switcher's OQ1–OQ3 are untouched.Scope
family:overlay-dismissal,spec:AST-027, and the toast viewport depend on them; the manual-popover host is named becausespec:AST-027FR8 requires top-layer hosting and the page-level band it replaces is a recorded gap.Validation
pnpm check:knowledge— "Knowledge templates and records are aligned." (on every head)pnpm check:sync— clean.git diff --check— clean.check:repochain (knowledge, sync, package boundaries, changesets, i18n, fixtures, setup contract, …) passed on the commit.