Repository navigation
spec(core): each BottomSheetSwitcher step owns its native dialog - #7744
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.
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 dialoganatomy inBottomSheetSwitcher.doc.mjsand 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]
| 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.", |
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
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.
7777e5e to
fd6a8d4
Compare
There was a problem hiding this comment.
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]
| 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. |
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
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` | |
There was a problem hiding this comment.
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.
fd6a8d4 to
0beae80
Compare
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
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().
| - **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. |
There was a problem hiding this comment.
"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.
0beae80 to
b578bbd
Compare
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
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).
| (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 |
There was a problem hiding this comment.
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.
b578bbd to
e79eac5
Compare
There was a problem hiding this comment.
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
onCancelto 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 | |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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).
| - 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. |
There was a problem hiding this comment.
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.
e79eac5 to
7d86354
Compare
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
"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.
7d86354 to
ed455a9
Compare
There was a problem hiding this comment.
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` | |
There was a problem hiding this comment.
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.
| 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. |
There was a problem hiding this comment.
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.
ed455a9 to
101ddc8
Compare
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
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.
101ddc8 to
452f7ae
Compare
There was a problem hiding this comment.
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]
|
Owner approval at exact head I approve the decisions recorded in this pull request:
Ruling on the compatibility question: this is a nonbreaking patch. It covers the switcher's |
Exact decision needed
The
BottomSheetSwitchercomponent 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:BottomSheetSwitcherFR2, 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'srefreach, 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'scancelis answered (one registration for the flow, gated by the active step'spurpose) but say nothing about hosting or scroll.spec:AST-027(current) — FR1/FR4 forbid a page-widez-indexband 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-013FR10 (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-017FR1/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-runtimeandarchitecture:public-component-apiINV8 — native dialog hosting; a ref "connects to the element promised by its contract". Neither names the switcher's promised element.BottomSheetSwitcherin 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:BottomSheetchanges 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 itsverified_bynames the engine spec.Decision
component:BottomSheetSwitcherDEC-1 — each step owns its native dialog; the switcher coordinates. A step is a BottomSheet with its own native dialog, presented withshowModal()in a scrim flow andshow()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); andcancelgated by the active step'spurposethrough 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
Dialogopened from a step; it is released after the flow's exit. No mechanism is named (knowledge-contractsINV18): not the shared hook, not:modal, not a stylesheet.component:BottomSheetSwitcherDEC-2 — the host dialog's identity is implementation, not contract. The switcher'sref,id, anddata-testidreach 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 nevernullwhile a sheet is mounted and identifying attributes appear once (FR9). The released contract is quoted from v0.7.1'sBottomSheetSwitcher.doc.mjsrather than paraphrased: "Ref forwarded to the one shared native dialog." and "Its ref and shared DOM props target that dialog."onCancelis not host-only: as #7720's code has it, the composedcancelhandler is attached to every sheet dialog (the browser raisescancelon 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 withnull, then with the new host. DEC-2 addresses thespec:AST-017FR1 scenario directly: code that copiedref.currentonce 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 readref.currentat 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, withid/data-testidon 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-027FR8 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); theshow()host is "verified current behavior"; and the page-level value the wrapper carries "remains aspec:AST-027conformance gap" —spec:AST-027FR3/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.
Dialogrelies 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.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 mechanismDialog,Drawer, andLightboxuse (the shareduseScrollLockhook), 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.cancelcarries no source, so a backdrop tap cannot be told from Escape forpurposegating, and the attribute is not available in WebKit.refas[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-testidand its compatibility class (Switcher FR9, DEC-2, Compatibility); BottomSheet's ownership and anatomy notes where per-step dialogs require them. Explicitly outside:sheetId, scrim theming target) stay open and keep their options.spec:AST-027conformance gap it already was; this record does not clear it.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_bylists 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:knowledgedoes 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 dialogin both the record andBottomSheetSwitcher.doc.mjsuntil the per-step implementation renders those dialogs; the rename (coupled, ascheck:knowledgerequires) 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 ascomponent:BottomSheetSwitcherDEC-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
ref.currentonce at mount keeps a real<dialog>…"), not as a change from a prior release. The Intent ofBottomSheetSwitcher.spec.mdsaid 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". Nopreviously,now, orproposesremains in the edited claims.DEC-1; spec:AST-027 FR2, FR4, FR8and 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:BottomSheetFR3's basis cites "issue Bottom Sheet: keyboard users cannot scroll the sheet body (axe serious) #5207"; flagged for the owner, not edited here.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.issue #5207in BottomSheet FR3 is noted under R3.BottomSheetSwitcher.spec.mdand OQ1 inBottomSheet.spec.mdare unanswered and keep their options; none of them is ruled by this record.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.BottomSheet.spec.md's verification map had the "Layout evidence" row glued onto the FR3/AR1–AR2 row onmain(11 cells on one line), now two rows. The Switcher Dialog-surface row keepsmain's eight cells (none/component:BottomSheetSwitcher/released/ "Component-owned semantics and handlers retain precedence"), with the owner-decision label in Meaning.:modal, stylesheet rule,scrollbar-gutter,<body>padding, "Core's other modal overlays", "carried before", "previously", "now", "proposes".Scope
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.showModal()) is named, because the family and layer contracts depend on it; no module, attribute, or stylesheet is contracted.Validation