Skip to content

spec(core): each BottomSheetSwitcher step owns its native dialog - #7744

Merged
imdreamrunner merged 1 commit into
mainfrom
spec/bottom-sheet-per-sheet-dialog
Oct 21, 2026
Merged

imdreamrunner merged 1 commit into
mainfrom
spec/bottom-sheet-per-sheet-dialog

Conversation

@imdreamrunner

@imdreamrunner imdreamrunner commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Records only. The anatomy part keeps its shipped name (Shared dialog) here, in the record's anatomy rows and in BottomSheetSwitcher.doc.mjs, until the implementation that renders per-sheet dialogs lands (#7720 renames it, coupled as check:knowledge requires). Every claim this PR rewrites is marked "Owner decision" in its review-state column: it is the decided requirement, not shipped behaviour; the Intent says so, and says the same of the representative states that name a sheet's own dialog, the retained dialog, or the flow wrapper.

Exact decision needed

The BottomSheetSwitcher component record describes a hosting the owner has ruled against, and no current record settles its replacement:

Where a switcher step's native dialog lives. component:BottomSheetSwitcher FR2, FR5, FR8, FR9, PR1, the Dialog-surface concept, the representative states, and the anatomy all describe one switcher-owned <dialog> hosting every step as a panel, with a script focus trap. Unresolved: does each step own its own native dialog as a standalone sheet does, what does the switcher then still own (the flow's one page scroll lock included), what does the switcher's ref reach, and is the change of ref target a breaking change?

Existing authority searched

  • component:BottomSheet (packages/core/src/BottomSheet/BottomSheet.spec.md, draft) — owns standalone presentation and the Scrim; its ownership bullets name "a shared switcher dialog or scrim" as the switcher's; its decision log has one entry (keyboard ownership).
  • component:BottomSheetSwitcher (packages/core/src/BottomSheet/BottomSheetSwitcher.spec.md, draft) — FR2/FR5/FR8/FR9/PR1 record the shared host as "verified shipped behavior" with no decision behind it; its decision log reads "None".
  • family:overlay-dismissal (docs/families/overlay-dismissal.md, current) — FR1–FR3 and FR7 govern Escape and platform-close routing through the shared stack; they constrain how a per-step dialog's cancel is answered (one registration for the flow, gated by the active step's purpose) but say nothing about hosting or scroll.
  • spec:AST-027 (current) — FR1/FR4 forbid a page-wide z-index band and DEC-2/DEC-4 put isolation on the component surface; FR7/FR8 require floating surfaces to use the native top layer. These bound the non-modal ordering of per-step dialogs but do not decide hosting.
  • spec:AST-013 FR10 (current) — browser-owned behaviour (modal containment, top-layer order, backdrop paint) needs real-browser evidence in the named browser; it governs the evidence test(core): browser evidence for per-sheet switcher dialogs #7757 carries.
  • spec:AST-017 FR1/FR3/FR7 (current) — compatibility covers public exports, props, defaults, documented interaction and accessibility behaviour; "internal implementation, undocumented markup, source layout, and exact appearance are not stable merely because a consumer can observe them." This is the authority the tier ruling rests on, but it does not itself say whether the switcher's dialog element identity is contract.
  • architecture:layer-runtime and architecture:public-component-api INV8 — native dialog hosting; a ref "connects to the element promised by its contract". Neither names the switcher's promised element.
  • Open work inspected: the two per-sheet-dialog prototypes this record settles between, and the open-order stacking candidate feat(core): BottomSheet stacks by open order, replaces on a same-update switch, and BottomSheetSwitcher is deprecated #7648 with its replace-handoff prototype. feat(core): BottomSheet stacks by open order, replaces on a same-update switch, and BottomSheetSwitcher is deprecated #7648 keeps a switcher flow as one stack level and deprecates BottomSheetSwitcher in favour of standalone sheets that replace each other; DEC-1 keeps the switcher as the coordinator of per-step dialogs. The two are alternative directions for the same component: landing either changes or obsoletes the other (five files conflict, both orders), and the owner decides which direction the switcher takes. Neither of these records settles that choice., and the earlier shared-dialog change whose stated reasons (one top-layer boundary, one backdrop, no portal) are addressed inside DEC-1.

No existing claim settles the decision; the canonical owner is extended rather than a new record created, because the fact sits inside the switcher's existing boundary (the sheet owns the dialog it renders; the switcher owns coordination). component:BottomSheet changes only where per-step dialogs require it: its ownership bullets and anatomy notes say the sheet owns the dialog it renders and the switcher coordinates that dialog's lifecycle and the flow's one Scrim, and its verified_by names the engine spec.

Decision

component:BottomSheetSwitcher DEC-1 — each step owns its native dialog; the switcher coordinates. A step is a BottomSheet with its own native dialog, presented with showModal() in a scrim flow and show() otherwise, so the browser supplies modality per step: inert page, focus containment, cancel, backdrop. The switcher owns only what no single dialog can decide: which dialog is presented, retained, or closed (FR2); the one scrim handed from the retained dialog to the entering one without a fade (FR8); re-presenting a sheet still open beneath a newer one when the flow returns to it (FR2); focus kept in the active sheet when a retained dialog closes and returned to the flow's opener when the flow ends (FR4); and cancel gated by the active step's purpose through the shared dismissal stack (FR6, FR7). Its one element is a flow wrapper: boxless in a modal flow; in a non-modal flow the isolated surface inside which the step dialogs order themselves with local values, so no new page band exists (FR5).

Page scroll lock (DEC-1, FR5, PR1, Ownership) — stated by its effects only. DEC-1's "owns only" list names the flow's one page scroll lock, held from the moment a modal flow is presented until after its exit so a handoff between two open dialogs never releases and re-acquires it. FR5 states the effects: the page does not scroll while a modal flow is presented; the lock is held continuously across a handoff, never released and re-acquired between steps; it still holds beneath a Dialog opened from a step; it is released after the flow's exit. No mechanism is named (knowledge-contracts INV18): not the shared hook, not :modal, not a stylesheet.

component:BottomSheetSwitcher DEC-2 — the host dialog's identity is implementation, not contract. The switcher's ref, id, and data-testid reach the dialog hosting the flow (the active sheet's; the exiting sheet's through its exit; while closed, the last host's or the first mounted sheet's), so an object ref is never null while a sheet is mounted and identifying attributes appear once (FR9). The released contract is quoted from v0.7.1's BottomSheetSwitcher.doc.mjs rather than paraphrased: "Ref forwarded to the one shared native dialog." and "Its ref and shared DOM props target that dialog." onCancel is not host-only: as #7720's code has it, the composed cancel handler is attached to every sheet dialog (the browser raises cancel on the dialog it presents, the active sheet's); DEC-2, FR9, the Dialog-surface row, #7720's prop doc, JSDoc, and Changeset all say so in those words. FR9 also states the handoff rule for callback refs: React's semantics — a returned cleanup runs on detach from the old host; a callback without one is called with null, then with the new host. DEC-2 addresses the spec:AST-017 FR1 scenario directly: code that copied ref.current once at mount (the v0.7.1 doc said "Ref forwarded to the one shared native dialog") still holds a real <dialog>, but after a handoff that element may no longer be the host; the guidance is to read ref.current at use time or use a callback ref. DEC-2 also states that DOM props and handlers given to the switcher (className, style, onCancel, onToggle, aria-*, …) reach every sheet dialog, with id/data-testid on the host only, so a handler fires for whichever sheet dialog raises the event; the owner rules the hosting — moving host and per-dialog props alike — nonbreaking under FR3 because no visual output and no public API surface changes. The Compatibility section records this class.

Non-modal path (FR5, PR1). The hasScrim=false / show() host is not an owner decision: spec:AST-027 FR8 wants top-layer hosting and lists this path as a confirmed exception, and the record's Ownership section calls it a migration gap. FR5 therefore splits its review state three ways: the modal host and the wrapper's local ordering are "Owner decision" (DEC-1: the sheet dialogs of a non-modal flow order themselves with local values inside the switcher's isolated flow wrapper); the show() host is "verified current behavior"; and the page-level value the wrapper carries "remains a spec:AST-027 conformance gap" — spec:AST-027 FR3/FR4 rule out calling that value a decision, and the Ownership section already labels it the gap. PR1 is scoped to a modal flow: one backdrop painted, a sheet dialog modal throughout, the one lock held for the whole flow; a non-modal flow paints no backdrop and holds no lock.

Rejected alternatives

Recorded as one line each inside the decision that rejects them; the reasoning here.

  • Keep the one shared dialog. It expresses "the switcher is the sheet" directly and gets one backdrop and one focus boundary for free, but a step is then not a sheet: every standalone-sheet mechanism (presentation, scrim, dismissal, focus return) needs a second switcher-only implementation, and a step cannot be reasoned about, stacked, or replaced the way a sheet can.
  • A custom coordinator with a script focus trap and blur-before-present. The trap redirects focus inside a dialog the browser already contains and fights native inertness (Core Dialog relies on native containment alone); blurring the control the user is pressing pre-empts the engine's focus record instead of correcting it, and depends on which element each engine records.
  • A CSS scroll lock keyed off dialog:modal. A stylesheet rule freezing the page's scroller while any sheet dialog is in the top layer would need no counter and no saved offset, and was prototyped and measured. Rejected by the owner: sheets keep the one mechanism Dialog, Drawer, and Lightbox use (the shared useScrollLock hook), so Core has one scroll lock to reason about, and the switcher holds it once for the whole flow.
  • closedby="any" for the backdrop tap. cancel carries no source, so a backdrop tap cannot be told from Escape for purpose gating, and the attribute is not available in WebKit.
  • Cross-fading the two backdrops during a handoff. The combined dim dips to roughly 0.44 mid-handoff; a hand-over without a fade keeps it steady.
  • Classifying the moving ref as [breaking]. No public API, documented interaction, accessibility semantics, or visual output changes; the released contract is that the ref reaches the flow's native dialog.

Claim scope and non-goals

The records govern only: per-step dialog ownership and the switcher's coordination duties, the flow's one scroll lock among them (Switcher FR2, FR5, FR8, PR1, DEC-1); the host-dialog resolution of ref/onCancel/id/data-testid and its compatibility class (Switcher FR9, DEC-2, Compatibility); BottomSheet's ownership and anatomy notes where per-step dialogs require them. Explicitly outside:

Evidence and implementation relationship

Two PRs stack on this record: per-step switcher dialogs (#7720, implements DEC-1/DEC-2 and the anatomy rename coupled to the record) and the Chromium + WebKit evidence (#7757). Their evidence — Chromium and WebKit traces of dim per frame (normal and reduced motion), focus, Tab, dismissal, the one lock held through a handoff and under a probe dialog with the offset restored after the exit, and the API audit — is attached to those PRs, not written here.

The verified_by lists and verification rows name the test file the evidence PR adds (BottomSheetNativeModal.a11y.browser.spec.ts); at this record's commit it does not exist yet. pnpm check:knowledge does not resolve those paths, so the record validates; the names are the contract the implementation meets.

No edit outside the records. The anatomy part stays Shared dialog in both the record and BottomSheetSwitcher.doc.mjs until the per-step implementation renders those dialogs; the rename (coupled, as check:knowledge requires) travels with #7720. This PR therefore carries no Changeset.

Claims needing owner approval (the records stay draft; nothing is self-approved): BottomSheet's Owns bullet and anatomy note, both marked as component:BottomSheetSwitcher DEC-1's decided structure rather than shipped anatomy; BottomSheetSwitcher DEC-1 and DEC-2; the rewritten FR2, FR5, FR8, FR9, PR1, the Dialog-surface concept row, the Closed/First open/Handoff/Modal states, the Intent, the Ownership bullets, and the Compatibility class. The decision-log entries record the owner's rulings and name the owner as decider; the owner's approval of this record is what makes them authoritative. The release tier (nonbreaking) is the owner's ruling, recorded in Switcher DEC-2 and the Compatibility class.

Writing rubric results

  • R1 narration: DEC-2's consumer guidance is stated as present-tense behaviour ("code that copies ref.current once at mount keeps a real <dialog>…"), not as a change from a prior release. The Intent of BottomSheetSwitcher.spec.md said the draft settles no hosting decision; rewritten to point at the decision log and to say that an "Owner decision" row is the decided requirement, not shipped behaviour. FR2/FR5/FR8/FR9 basis cells said "Current source, docs, and tests" for behaviour that is now a decision; rewritten to name the decision (DEC-1, DEC-2) and the review state to "Owner decision". No previously, now, or proposes remains in the edited claims.
  • R2 research: none in either record; the comparison of the two prototypes, the engine measurements, and the stacking-candidate analysis live in the implementation PRs and a linked comparison artifact.
  • R3 evidence: FR5's basis is DEC-1; spec:AST-027 FR2, FR4, FR8 and the verification map names test layers (BottomSheetSwitcher.test.tsx, the engine spec), not changes. One pre-existing R3/R5 finding left untouched because it is an unrelated claim: component:BottomSheet FR3's basis cites "issue Bottom Sheet: keyboard users cannot scroll the sheet body (axe serious) #5207"; flagged for the owner, not edited here.
  • R4 rejected alternatives: BottomSheetSwitcher DEC-1 three (shared dialog, script trap, page-level z-index), DEC-2 one (a breaking tier); each one sentence, inside its decision. The CSS lock is rejected in this PR body, not in a record: no record ever named it.
  • R5 change references: none added. The pre-existing issue #5207 in BottomSheet FR3 is noted under R3.
  • R6 open questions: OQ1–OQ3 in BottomSheetSwitcher.spec.md and OQ1 in BottomSheet.spec.md are unanswered and keep their options; none of them is ruled by this record.
  • Per-step claims under a "current" label (script-swept, every FR, state, anatomy and design row in both records): the Scrim design row's one-painter clause now reads "DEC-1 decides that one sheet dialog paints it at a time", cites FR5 and FR8, and names DEC-1 in its authority cell; the Sheet dialogs row names DEC-1 in its authority cell the same way; the representative-states table opens with the sentence that its per-step states are DEC-1's decided structure; BottomSheet's "Does not own" bullet about the switcher is marked DEC-1's decided structure too. FR1, FR3, FR4 and FR6 are main's text verbatim (shipped behaviour that still holds) and keep "Verified shipped behavior". No other row mixes a per-step claim with a current/shipped label.
  • Table shape: every row in both records has its header's cell count (script-checked). One drift fixed: BottomSheet.spec.md's verification map had the "Layout evidence" row glued onto the FR3/AR1–AR2 row on main (11 cells on one line), now two rows. The Switcher Dialog-surface row keeps main's eight cells (none / component:BottomSheetSwitcher / released / "Component-owned semantics and handlers retain precedence"), with the owner-decision label in Meaning.
  • Terms absent from both records, checked: :modal, stylesheet rule, scrollbar-gutter, <body> padding, "Core's other modal overlays", "carried before", "previously", "now", "proposes".

Scope

  • This PR contains specification records only.
  • It records observable behavior and preserves equivalent internal implementations: FR5 names the lock's effects (no scroll while presented, held across a handoff, holds under a Dialog, released after the exit) and that it is the lock Core's modal overlays share; the host dialog's resolution is stated by what the ref and attributes reach, not by selectors or module names.
  • Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract: only the native modal-dialog state (showModal()) is named, because the family and layer contracts depend on it; no module, attribute, or stylesheet is contracted.
  • Verification states the evidence layer and failure signal without prescribing CI job/workflow topology.
  • It records one intentional durable decision (how switcher steps' dialogs are hosted), not a rescue for a separable tagalong.
  • The canonical knowledge owner, affected current claims, and overlapping open work are named; no ownership collision: the sheet owns the dialog it renders, the switcher owns coordination and the flow's lock.
  • Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them.
  • Public text and artifacts contain no internal Meta context.

Validation

pnpm check:knowledge        Knowledge templates and records are aligned.
pnpm exec prettier --check packages/core/src/BottomSheet   All matched files use Prettier code style!
git diff --check            clean
pnpm check:changesets       ✓ (no Changeset: records only)
node scripts/release/changeset-coverage.mjs pr --base origin/main --head HEAD --released v0.7.1   ✓ nothing ships to consumers

@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 21, 2026 6:47am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🔄 In progress (for maintainers only) Oct 21, 2026, 3:15 PM UTC

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Current run: The current change has no visual or preview-producing scope. Visual checks are not applicable; preview links are not shown.

The exact-head preview is not available yet.


Generated by PR Enrichment workflow | View current CI run

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheet and BottomSheetSwitcher records (BottomSheet FR5/DEC-2; BottomSheetSwitcher DEC-1/DEC-2 with the rewritten FR2, FR5, FR8, FR9 and PR1), including whether a switcher ref that moves to a different dialog on every handoff is nonbreaking under AST-017 FR1/FR3. Two fixes are needed whatever that decision is:

  • Keep the Shared dialog anatomy in BottomSheetSwitcher.doc.mjs and the record's anatomy rows, and drop the patch changeset, until the per-step implementation lands; today the docs and the release note describe dialogs the component does not render.
  • State BottomSheet FR5/DEC-2 by their observable effects, or record the root-stylesheet mechanism as an intentional public contract (knowledge-contracts INV18).

[Automated review]

Comment on lines +6 to +9
name: 'Sheet dialogs',
required: true,
description:
'One native dialog that owns modality, focus, dismissal, and lifecycle for the complete flow.',
"One native dialog per child sheet, presented and closed by the switcher; the dialog hosting the active sheet carries the flow's ref, id, modality, and dismissal.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This anatomy entry now describes one native dialog per child sheet, but the shipped switcher still renders one shared dialog, and this file's description, ref and hasScrim docs (lines 145, 150, 176) still say so. Keep Shared dialog here and in the record's anatomy rows, and drop the patch changeset, until the per-step implementation lands.

| FR2 | The Sheet panel carries `bottom-sheet`; Content area, Handle, and Scrim carry no BottomSheet public target. | Current source, docs, and tests | Verified current behavior; no target change |
| FR3 | Content renders in one real observed content box. Effective overflow, containment, and keyboard access come from `useScrollableArea`; the named overflowing body delegates forward Tab entry to a safe first sequential native link/button. | `spec:AST-025`; issue #5207; tests | Focus-time prototype for owner review |
| FR4 | The Content area pads caller content by the sheet's container inset and publishes the applied inset to descendants as a container publisher. | `architecture:container-padding` | Admitted; implementation pending |
| FR5 | Page scroll is locked exactly while a sheet dialog is modal, by a stylesheet rule on the document root keyed off the browser's modal-dialog state, with no inline styles on `<body>` and the viewport's scroll offset unchanged. A classic scrollbar's gutter is held open only when the scrollbar takes layout space when the sheet is presented. | DEC-2; `spec:AST-013` FR10 | Owner decision; real-device iOS touch evidence owed |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR5 prescribes the mechanism ("by a stylesheet rule on the document root keyed off the browser's modal-dialog state"). The knowledge-contracts architecture (INV18) asks a new component requirement to state observable effects unless the exact mechanism is an intentional public contract. Consider keeping the effects here (no page scroll while a sheet dialog is modal, no inline <body> styles, offset unchanged, gutter condition) and moving the mechanism to the layer-runtime architecture or the implementation.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheet and BottomSheetSwitcher records: BottomSheet FR5/DEC-2, and BottomSheetSwitcher DEC-1/DEC-2 with the rewritten FR2, FR5, FR8, FR9, PR1 and Compatibility class. The ruling to settle is whether a switcher ref that moves to a different dialog on every handoff is nonbreaking under AST-017 FR1/FR3, given that v0.7.1 documents it as "Ref forwarded to the one shared native dialog." The two fixes asked for on the earlier revision are done. The inline notes on FR9 and the FR5 verification row are wording points for that decision. While both records stay draft, these decisions will not yet govern the stacked implementation PRs.

[Automated review]

Comment on lines +245 to +248
on a handoff, is implementation: the released contract is that the ref reaches
the flow's native dialog, and public props, defaults, exports, and visual
output are unchanged, so this hosting is a nonbreaking change under
`spec:AST-017` FR3.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

v0.7.1 documents this prop as "Ref forwarded to the one shared native dialog." (BottomSheetSwitcher.doc.mjs). Under AST-017 FR3, then, the dialog the ref reaches is an explicitly documented extension point, not undocumented markup. Before this classes the move nonbreaking, it should address AST-017 FR1's consumer scenario, such as code that keeps ref.current from mount. This is the ruling the maintainer is asked for.

| FR6 | The active child purpose governs implicit dismissal: `info` allows Escape, scrim click, and swipe; `form` allows Escape; `required` blocks all three and exposes `alertdialog`. | Current BottomSheet docs and switcher tests | Verified shipped behavior |
| FR7 | While visible, the switcher participates in `family:overlay-dismissal`; one Escape or platform close request reaches only the topmost present layer, and descendants receive a deeper logical layer scope. | `family:overlay-dismissal` and the audit regression test | Settled remediation in this audit |
| FR8 | One backdrop paints the flow's scrim at a time: the retained sheet's dialog keeps it through a handoff and the entering dialog takes it over without a fade when the retained dialog closes. A stale gesture from an outgoing sheet cannot change the flow's scrim, and unmounting a retained sheet cannot keep its dialog open or the flow's modal layer held. | DEC-1; current tests | Owner decision |
| FR9 | The switcher forwards neutral dialog attributes, styling inputs, and composed handlers to every sheet dialog; renders `id` and `data-testid` on the host dialog only, so each appears once in the document; attaches its ref to the host dialog with React callback-ref cleanup semantics, never `null` while a sheet is mounted; and preserves its owned ARIA and dismissal behavior. | DEC-1, DEC-2; `architecture:public-component-api` INV8 | Owner decision |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Under React 19, a callback ref that returns no cleanup is called with null when it detaches from the old host. For that caller, "React callback-ref cleanup semantics" and "never null while a sheet is mounted" cannot both hold on a handoff. Say which one wins.

| FR2 | Panel target assertion, source inspection, and theming target inventories | Sheet panel, Content area, Handle, and Scrim | Removing the panel target or documenting an unshipped child target fails evidence or inventory. | `audit:BottomSheet/theming` |
| FR3, AR1–AR2 | `BottomSheetKeyboard.test.tsx`; `BottomSheetKeyboard.a11y.browser.spec.ts`; open Storybook stories | fitting/overflow, plain text, usable/unusable/dynamic descendants, focus | Missing/duplicate keyboard access, stale ownership, focus movement, or broken content-box layout fails. | `audit:BottomSheet/accessibility` | | Layout evidence | `BottomSheetPanel.test.tsx` | Floating Handle and scrolling Content area | Reordering or merging the stable parts fails existing panel structure and style assertions. | `audit:BottomSheet/anatomy` |
| FR4 | `BottomSheetPanel.test.tsx` container padding tests; `derivedVarRegistry.test.ts` | default, `padding` prop, theme `padding`, lone padded Section child | The published inset differs from the applied padding, or a lone padded Section changes geometry. | `audit:BottomSheet/anatomy` |
| FR5 | `sheetScrollLock.test.ts`; `BottomSheet.test.tsx` native modal host suite; `BottomSheetScrollLock.a11y.browser.spec.ts` in Chromium and WebKit; real-device iOS Safari touch check | modal, non-modal, beneath a Dialog, through a handoff, with and without a classic scrollbar, a `<body>` that owns the viewport scroll, an engine without `scrollbar-gutter` | The page scrolls behind a modal sheet, `<body>` gains inline lock styles, the offset differs after release, the lock outlives the last modal sheet dialog, a gutter is reserved on a page that had no scrollbar, or the page reflows sideways where `scrollbar-gutter` is unsupported. | `audit:BottomSheet/behavior` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The "<body> gains inline lock styles" signal fires in this row's own "beneath a Dialog" state, because Core Dialog keeps the shared body-pin hook. Scope the signal to the sheet's own lock.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheet and BottomSheetSwitcher records: BottomSheet FR5/DEC-2, and BottomSheetSwitcher DEC-1/DEC-2 with the rewritten FR2, FR5, FR8, FR9, PR1 and Compatibility class. The ruling to settle is whether a switcher ref that follows the hosting dialog, and DOM handlers such as onToggle that reach every sheet dialog, are nonbreaking under AST-017 FR1/FR3, given that v0.7.1 documents "Ref forwarded to the one shared native dialog." One fix is needed whatever that ruling is: Switcher FR5 now makes the non-modal show() host an owner decision, but AST-027 FR8 requires top-layer hosting for that path and the record's own relationships section calls it a migration gap, so keep show() as current behaviour with "the non-modal host remains a spec:AST-027 conformance gap", or state that clause by its effects. The callback-ref and FR5 verification-row points from the last revision are resolved.

[Automated review]

| FR2 | Each child sheet owns one native dialog. Opening a sheet presents its dialog once; a handoff presents the entering sheet's dialog above the retained one and closes the retained dialog only after its panel has faded. Returning to a sheet whose dialog is still open beneath a newer one re-presents it so the interactive sheet is the topmost layer. | DEC-1 | Owner decision |
| FR3 | During a handoff, the entering sheet is above the retained sheet. A taller retained sheet aligns down to a shorter entering sheet, and the retained sheet fades only after required transform motion completes. | Current source and transition tests | Verified shipped behavior; exact visual treatment remains human-reviewed |
| FR4 | Closing retains the outgoing sheet through its exit, then closes the dialog. A modal flow restores focus to the element captured when that modal flow opened. | Current source and focus tests | Verified shipped behavior |
| FR5 | `hasScrim=true` uses `showModal()`, a native backdrop, native focus containment, and the page scroll lock of `component:BottomSheet` FR5. `hasScrim=false` uses `show()` without a backdrop or scroll lock, orders the sheet dialogs inside the switcher's isolated flow wrapper with local values only, and leaves the page interactive. | DEC-1; `component:BottomSheet` DEC-2; `spec:AST-027` FR2, FR4 | Owner decision; the non-modal wrapper's page-level value remains the `spec:AST-027` conformance gap |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This row now decides that hasScrim=false uses show() and narrows the open gap to the wrapper's page-level value. AST-027 FR8 asks a sheet that must escape ordinary content to enter the browser top layer ("a fixed position, or a larger number is not a substitute"), AST-027 lists the non-modal BottomSheetSwitcher path among its confirmed exceptions, and lines 194-195 of this record still call that path a migration gap. Keep the non-modal host out of the decision: mark this clause verified current behaviour with "the non-modal host remains a spec:AST-027 conformance gap", or state it by its effects without naming show().

Comment on lines +138 to +141
- **PR1 — One painted scrim.** At rest exactly one sheet dialog is presented;
during a handoff two are, with one backdrop painted. Native modality and the
page scroll lock hold across child handoffs because a sheet dialog is modal
throughout.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"One backdrop painted" and "a sheet dialog is modal throughout" hold only in a modal flow; in a hasScrim={false} flow FR5 has no backdrop or scroll lock. Consider scoping PR1 to a modal flow.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheet and BottomSheetSwitcher records (BottomSheet FR5/DEC-2; BottomSheetSwitcher DEC-1/DEC-2 with the rewritten FR2, FR5, FR8, FR9, PR1 and Compatibility class), including whether a switcher ref that follows the hosting dialog, and handlers such as onToggle that reach every sheet dialog, are nonbreaking under AST-017 FR1/FR3 given that v0.7.1 documents "Ref forwarded to the one shared native dialog." Two record fixes are needed whatever that ruling is:

  • Switcher FR5 labels the non-modal flow wrapper and its sheet dialogs "verified current behavior", but the shipped switcher has one shared dialog and no wrapper, so mark that sentence as DEC-1's decision in the present tense.
  • BottomSheet DEC-2 says the lock uses no inline styles on <body>, while FR5's gutter fallback is a <body> padding, so give DEC-2 FR5's "no inline positioning or overflow" wording.

[Automated review]

| FR2 | Each child sheet owns one native dialog. Opening a sheet presents its dialog once; a handoff presents the entering sheet's dialog above the retained one and closes the retained dialog only after its panel has faded. Returning to a sheet whose dialog is still open beneath a newer one re-presents it so the interactive sheet is the topmost layer. | DEC-1 | Owner decision |
| FR3 | During a handoff, the entering sheet is above the retained sheet. A taller retained sheet aligns down to a shorter entering sheet, and the retained sheet fades only after required transform motion completes. | Current source and transition tests | Verified shipped behavior; exact visual treatment remains human-reviewed |
| FR4 | Closing retains the outgoing sheet through its exit, then closes the dialog. A modal flow restores focus to the element captured when that modal flow opened. | Current source and focus tests | Verified shipped behavior |
| FR5 | `hasScrim=true` uses `showModal()`, a native backdrop, native focus containment, and the page scroll lock of `component:BottomSheet` FR5 (owner decision, DEC-1). `hasScrim=false` uses `show()` without a backdrop or scroll lock and leaves the page interactive; the sheet dialogs of a non-modal flow order themselves with local values inside the switcher's isolated flow wrapper, which carries the one page-level value the host carried before (verified current behavior; the non-modal host remains a `spec:AST-027` conformance gap, not a decision). | DEC-1; `component:BottomSheet` DEC-2; `spec:AST-027` FR2, FR4, FR8 | Modal: owner decision. Non-modal: verified current behavior; the non-modal host remains a `spec:AST-027` conformance gap |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This non-modal clause is labelled verified current behaviour, but the shipped switcher has one shared <dialog> (which itself carries zIndex: 1000 in a no-scrim flow) and no flow wrapper or per-sheet dialogs; the wrapper is DEC-1's decision. Keep "hasScrim=false uses show() without a backdrop or scroll lock and leaves the page interactive" as current behaviour with the AST-027 gap, and mark the wrapper sentence as an owner decision in the present tense ("the host carried before" narrates a change).

Comment on lines +255 to +256
(FR5): it holds exactly while any sheet dialog is in the top layer, without a
lock counter, a saved scroll offset, or inline styles on `<body>`, so any

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

DEC-2 rules out any inline style on <body>, but FR5 and its verification row rule out only inline positioning or overflow, and FR5 makes the fallback for engines without scrollbar-gutter a <body> padding, which the stacked implementation applies inline. Use FR5's wording here, and keep the no-counter, no-saved-offset point to the lock itself.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheetSwitcher and BottomSheet records: Switcher DEC-1/DEC-2 with the rewritten FR2, FR5, FR8, FR9, PR1 and Compatibility class, and BottomSheet's ownership and anatomy bullets. That ruling includes whether a ref that follows the hosting dialog can be nonbreaking under AST-017 FR1/FR6, given that v0.7.1 documents "Ref forwarded to the one shared native dialog" and DEC-2 itself asks callers to change how they read it. Whatever the ruling, these fixes are needed:

  • Switcher FR5 marks the non-modal wrapper's page-level value as an owner decision, which AST-027 FR3/FR4 rule out; keep that value under the existing conformance-gap label.
  • DEC-2 sends onCancel to the host dialog in one sentence and to every sheet dialog in another, and the Dialog-surface row and FR9 split the same way; name it in one place.
  • BottomSheet's new Owns bullet and switcher-managed state describe per-sheet dialogs as current anatomy; mark them as the switcher's DEC-1 structure, as the switcher record's Intent does.
  • FR5's scroll-lock clause cites DEC-1, which decides no lock, and names "the lock Core's other modal overlays use"; add the flow-wide lock to DEC-1 and state it by its effects.
  • Retitle the pull request: it still announces a CSS scroll lock that this head no longer contains.

[Automated review]

| FR2 | Each child sheet owns one native dialog. Opening a sheet presents its dialog once; a handoff presents the entering sheet's dialog above the retained one and closes the retained dialog only after its panel has faded. Returning to a sheet whose dialog is still open beneath a newer one re-presents it so the interactive sheet is the topmost layer. | DEC-1 | Owner decision |
| FR3 | During a handoff, the entering sheet is above the retained sheet. A taller retained sheet aligns down to a shorter entering sheet, and the retained sheet fades only after required transform motion completes. | Current source and transition tests | Verified shipped behavior; exact visual treatment remains human-reviewed |
| FR4 | Closing retains the outgoing sheet through its exit, then closes the dialog. A modal flow restores focus to the element captured when that modal flow opened. | Current source and focus tests | Verified shipped behavior |
| FR5 | `hasScrim=true` uses `showModal()`, a native backdrop, and native focus containment, and locks page scroll with the lock Core's other modal overlays use: the page does not scroll while a modal flow is presented; the lock is held continuously across a handoff, never released and re-acquired between steps; it still holds beneath a `Dialog` opened from a step; it is released after the flow's exit (owner decision, DEC-1). `hasScrim=false` uses `show()` without a backdrop or scroll lock and leaves the page interactive (verified current behavior; the non-modal host remains a `spec:AST-027` conformance gap). In a non-modal flow the sheet dialogs order themselves with local values inside the switcher's isolated flow wrapper, which carries the flow's one page-level value (owner decision, DEC-1). | DEC-1; `spec:AST-027` FR2, FR4, FR8 | Modal host and flow wrapper: owner decision. Non-modal `show()` host: verified current behavior; it remains a `spec:AST-027` conformance gap |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The wrapper's "one page-level value" is labelled an owner decision here, but AST-027 FR3 ("The outer isolation owner MUST remain at its normal parent paint level") and FR4 rule out a page band, and lines 197-198 call that path a migration gap, not an exception. Keep the local ordering inside the wrapper as DEC-1's decision and put the wrapper's page-level value under the existing "remains a spec:AST-027 conformance gap" label.

Comment on lines +251 to +254
read `ref.current` at use time, or use a callback ref, to follow the flow. DOM
props and handlers given to the switcher (`className`, `style`, `onCancel`,
`onToggle`, `aria-*`, and the rest) reach every sheet dialog, with `id` and
`data-testid` on the host only, so a handler fires for whichever sheet dialog

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

onCancel is listed here among the props that reach every sheet dialog, but line 243 and the Dialog-surface row (line 90) send it to the host dialog only, and FR9 forwards "composed handlers to every sheet dialog". Name it in one place.

Comment on lines +247 to +251
identifying attributes appear once. Which element that is, and that it changes
on a handoff, is implementation: the released contract is that the ref reaches
the flow's native dialog. Code that copies `ref.current` once at mount keeps a
real `<dialog>`, but after a handoff that element may no longer be the host;
read `ref.current` at use time, or use a callback ref, to follow the flow. DOM

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

v0.7.1 documents "Ref forwarded to the one shared native dialog." and "Its ref and shared DOM props target that dialog." (BottomSheetSwitcher.doc.mjs), not "the flow's native dialog". The guidance that follows (read ref.current at use time, or use a callback ref) is a source change for a v0.7.1 caller, which AST-017 FR1 calls breaking and FR6 says approval cannot change; that ruling is what the maintainer is asked for.

Comment on lines +220 to +226
backdrop. The switcher owns only what no single dialog can decide: which dialog
is presented, retained, or closed; the one scrim handed from the retained
dialog to the entering one without a fade; re-presenting a sheet that is still
open beneath a newer one when the flow returns to it; focus kept in the active
sheet when a retained dialog closes; focus returned to the flow's opener; and
`cancel` gated by the active step's `purpose` through the shared dismissal
stack. The switcher's one element is a flow wrapper with no box in a modal

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FR5, PR1 and the Owns bullet cite DEC-1 for the flow's one page scroll lock, but this "owns only" list has no lock. Add it here, and state FR5's lock by its effects rather than "the lock Core's other modal overlays use" (knowledge-contracts INV18).

Comment on lines +133 to +134
- A switcher-managed sheet renders the same panel anatomy in its own dialog while
BottomSheetSwitcher coordinates that dialog's lifecycle and the flow's one Scrim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This, and the Owns bullet on line 97, describe a dialog per switcher-managed sheet, which shipped code does not render (a switcher item is a panel inside the switcher's shared dialog), in a record whose Intent says it records current consumer anatomy. Mark both as component:BottomSheetSwitcher DEC-1's decided structure, as the switcher record's Intent does.

@imdreamrunner imdreamrunner changed the title spec(core): BottomSheet locks page scroll by CSS; each BottomSheetSwitcher step owns its native dialog spec(core): each BottomSheetSwitcher step owns its native dialog Oct 21, 2026
@imdreamrunner
imdreamrunner force-pushed the spec/bottom-sheet-per-sheet-dialog branch from e79eac5 to 7d86354 Compare October 21, 2026 02:48

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheetSwitcher and BottomSheet records: Switcher DEC-1 and DEC-2 with the rewritten FR2, FR5, FR8, FR9, PR1, Dialog-surface row and Compatibility class, and BottomSheet's ownership and anatomy notes. That ruling includes whether a ref that follows the hosting dialog can be classed nonbreaking under AST-017 FR1 and FR6, given that v0.7.1 documents it as "Ref forwarded to the one shared native dialog" and DEC-2 itself asks callers to change how they read it. Whatever the ruling, these record fixes are needed:

  • DEC-2 says the released contract is that the ref reaches "the flow's native dialog"; quote the v0.7.1 text ("the one shared native dialog", "Its ref and shared DOM props target that dialog") and state the ruling against it.
  • The Dialog-surface row has five of its eight cells, so it renders with no owner, stability or precedence rule and with "Owner decision" as its default; restore those cells.
  • DEC-2 promises a ref that is never null, which FR9 limits to object refs; say "an object ref".

[Automated review]

| Shared presentation | `true` / `false` through `hasScrim` | Selects the current modal scrim-backed host or non-modal no-scrim host | Entire flow | `true` | `component:BottomSheetSwitcher` | released | Boolean only |
| Child identity | Non-empty `sheetId` on each direct BottomSheet child | Associates controlled selection, labeling, purpose, and transition state with a child | Direct nested BottomSheet children | none | `component:BottomSheet` | released | Empty ids warn and stay hidden; duplicate-id behavior is not specified |
| Dismissal request | `onActiveSheetChange(null)` | Reports an allowed implicit close without taking control from the caller | Escape/platform close, scrim click, or swipe according to active child purpose | none | `component:BottomSheetSwitcher`; `family:overlay-dismissal` owns ordering | released | The caller may retain its controlled value |
| Dialog surface | inherited dialog props plus `ref` and `onCancel` | Dialog props and handlers, `onCancel` included, reach every sheet dialog; `ref`, `id`, and `data-testid` resolve to the host dialog, which is never null while a sheet is mounted | Entire flow | Owner decision |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This row has five cells under the eight-column header (line 84), so it renders Owner decision as the Default and leaves Owner, Stability and Invalid-value behavior empty. The base row had none, component:BottomSheetSwitcher, released and "Component-owned semantics and handlers retain precedence". Restore those cells and carry the owner-decision label in the Meaning cell.

The `ref`, `id`, and `data-testid` of the switcher reach the dialog hosting
the flow: the active sheet's, the exiting sheet's through its exit, and, while
the flow is closed, the dialog that hosted it last or the first mounted
sheet's, so the ref is never `null` while a sheet is mounted and the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"the ref is never null while a sheet is mounted" holds only for an object ref: FR9 (line 107) says a callback ref without a cleanup is called with null, then with the new host, on every handoff. Say "an object ref" here.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheetSwitcher and BottomSheet records: Switcher DEC-1 and DEC-2 with the rewritten FR2, FR5, FR8, FR9, PR1, Dialog-surface row and Compatibility class, and BottomSheet's ownership and anatomy notes. That ruling includes whether a ref that follows the hosting dialog can be classed nonbreaking under AST-017 FR1 and FR6, given that v0.7.1 documents it as "Ref forwarded to the one shared native dialog" and DEC-2 itself asks callers who keep ref.current from mount to change how they read it. The earlier record fixes are in; one small one remains: the FR9 verification row still fails a run when "the ref is null while a sheet is mounted", which FR9 allows for a callback ref, so it should say "an object ref".

[Automated review]

| ---------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| FR1–FR4, FR8 | `BottomSheetSwitcher.test.tsx` | closed, first open, handoffs in either completion order, rapid replacement, unmount, close | A child becomes interactive at the wrong time, a retained child disappears early, a second backdrop paints, or a sheet dialog's lifecycle breaks. | `audit:BottomSheetSwitcher/behavior` |
| FR5–FR7, AR1–AR3 | `BottomSheetSwitcher.test.tsx`; `BottomSheetNativeModal.a11y.browser.spec.ts` | modal/non-modal, info/form/required, nested layer, IME, focus return, scroll lock in engines, non-modal local ordering | Modality, dismissal order, naming, inertness, scroll lock, or focus behavior diverges, or a sheet dialog takes a page-level z-index. | `audit:BottomSheetSwitcher/accessibility` |
| 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, the ref is null while a sheet is mounted or skips a cleanup, or an input overrides component-owned semantics accidentally. | `audit:BottomSheetSwitcher/api` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This failure signal still says "the ref is null while a sheet is mounted", while FR9 (line 107) has a callback ref without a cleanup called with null on detach, then with the new host; DEC-2 and the Dialog-surface row now say "an object ref". Say "an object ref is null while a sheet is mounted" here too, so a test written from this row does not reject the callback-ref behaviour FR9 requires.

Comment on lines +259 to +263
raises the event. The owner rules this hosting — the moving host and the
per-dialog props alike — nonbreaking under `spec:AST-017` FR3 because no
visual output and no public API surface changes.
Rejected: classifying the moving ref as a breaking change, because no public
API, documented interaction, or visual output changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This ruling and the Compatibility class (lines 49-52) call the moving host nonbreaking, but AST-017 FR1 counts a change as breaking when a valid v0.7.1 scenario must change source, and lines 253-255 name one: code that copies ref.current at mount. FR3's nonbreaking clause covers appearance-only changes, its covered surface includes callbacks and documented interaction, and v0.7.1 documents this target ("Its ref and shared DOM props target that dialog."); FR6 adds that engineering approval does not make an incompatible released contract nonbreaking. The class needs the maintainer's ruling, and if the change stands as written it needs the [breaking] lifecycle and migration that public-component-api INV9 requires, with "Migration decision" (line 54) to match.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheetSwitcher and BottomSheet records: Switcher DEC-1 and DEC-2 with the rewritten FR2, FR5, FR8, FR9, PR1, Dialog-surface row and Compatibility class, and BottomSheet's ownership and anatomy notes. That ruling includes whether a ref that follows the hosting dialog can be classed nonbreaking under AST-017 FR1 and FR6, given that v0.7.1 documents it as "Ref forwarded to the one shared native dialog" and DEC-2 itself asks callers who keep ref.current from mount to change how they read it. The FR9 verification-row fix is in; one small fix remains either way: the Scrim design row (BottomSheetSwitcher.spec.md:164) says "one sheet dialog paints it at a time" under "Current source and public docs", so it should cite FR8 and be marked as DEC-1's decided structure, as the Shared dialog row is.

[Automated review]

| ---------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -------------- | ------------------ |
| Shared dialog | Transparent host for the flow's interaction and accessibility boundary; DEC-1 decides one such host per child sheet | Current source and public docs | Structural | FR2, FR5, AR1 |
| Sheet panels | Delegate visible panel, content, handle, and gesture presentation to BottomSheet | `component:BottomSheet` draft as observational context | Prominent | FR1, FR3 |
| Scrim | Optional native backdrop that dims and blocks the page in modal presentation; one sheet dialog paints it at a time | Current source and public docs | Supporting | FR5 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This row now says "one sheet dialog paints it at a time", which is FR8's rule and DEC-1's decided structure, but it keeps "Current source and public docs" as its representation authority and cites only FR5, while the shipped switcher and the v0.7.1 docs have one shared dialog with "one ::backdrop across the whole flow". Cite FR8 here and mark the clause as DEC-1's decided structure, as the Shared dialog row does with "DEC-1 decides".

Records the owner's decisions for BottomSheetSwitcher: every child sheet
renders its own native dialog, presented and closed by the switcher (DEC-1),
and the identity of the dialog hosting the flow's ref, id, and dismissal is
implementation, not contract, so the hosting change is nonbreaking (DEC-2).
The page scroll lock is the one Core's modal overlays share, held once for
the whole modal flow and stated by its effects. BottomSheet's record changes
only where per-step dialogs require it: the sheet owns the dialog it renders;
the switcher coordinates that dialog's lifecycle and the flow's scrim.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a maintainer approving, amending or rejecting the new draft requirements in the BottomSheetSwitcher and BottomSheet records: Switcher DEC-1 and DEC-2 with the rewritten FR2, FR5, FR8, FR9, PR1, Dialog-surface row and Compatibility class, and BottomSheet's ownership and anatomy notes. That ruling includes whether a ref that follows the hosting dialog can be classed nonbreaking under AST-017 FR1 and FR6, since v0.7.1 documents it as “Ref forwarded to the one shared native dialog” and DEC-2 itself asks callers who keep ref.current from mount to change how they read it. No other fix is outstanding at this head.

[Automated review]

@imdreamrunner
imdreamrunner marked this pull request as ready for review October 21, 2026 15:04
@github-actions github-actions Bot added the spec-auto-merge Auto-merge was enabled by the spec owner gate label Oct 21, 2026
@imdreamrunner

Copy link
Copy Markdown
Contributor Author

Owner approval at exact head 452f7ae316eb6bdc57aa5d14b439bfc766fac3d3.

I approve the decisions recorded in this pull request:

  • component:BottomSheetSwitcher DEC-1: each step owns its native dialog. The switcher coordinates presentation and retention, the one-scrim handoff, re-presenting a returning step, flow-level focus return, purpose-gated cancel, and one page scroll lock for the whole flow.
  • component:BottomSheetSwitcher DEC-2: the host dialog's identity is implementation, not contract.
  • The rewritten FR2, FR5, FR8, FR9 and PR1, the Dialog-surface row, the Compatibility class, and the component:BottomSheet ownership and anatomy notes that mark the per-step structure as DEC-1's decision.

Ruling on the compatibility question: this is a nonbreaking patch. It covers the switcher's ref following the dialog that hosts the flow, and dialog props and handlers inherited from BaseProps<HTMLDialogElement> (for example onToggle) reaching every step's dialog. There is no visual or declared public API change, and BottomSheetSwitcher is planned for deprecation (#7648), so no compatibility layer will be added for these behaviours.

@imdreamrunner
imdreamrunner merged commit 0d5d64e into main Oct 21, 2026
52 checks passed
@github-actions
github-actions Bot deleted the spec/bottom-sheet-per-sheet-dialog branch October 22, 2026 06:22

This branch was successfully deployed

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

Labels

CLA Signed This label is managed by the Meta Open Source bot. spec-auto-merge Auto-merge was enabled by the spec owner gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant