Repository navigation
test(core): browser evidence for per-sheet switcher dialogs - #7757
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
Approval of this evidence PR waits on a maintainer decision: the behavior its four browser tests commit as expected (each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, and a CSS page-scroll lock with no <body> styles) is governed only by component:BottomSheetSwitcher DEC-1/FR2/FR8 and component:BottomSheet FR5/DEC-2, which are still draft, so a maintainer needs to approve or reject those records as proposed in #7744. Separately, the Tab test's not.toBe('retained') check (line 139) cannot fail: its loop starts 900 ms after Continue, after the retained dialog has already closed, and a control inside that dialog can never take focus. Please press Tab while both dialogs are open and assert that focus lands inside the "Notification frequency" dialog, or drop the "never the retained dialog" claim from the file header and the description. The four tests otherwise pass in Chromium and WebKit.
[Automated review]
| await page.waitForTimeout(900); | ||
| for (let index = 0; index < 12; index += 1) { | ||
| await page.keyboard.press('Tab'); | ||
| const where = await page.evaluate(() => { | ||
| const active = document.activeElement; | ||
| if (active == null || active === document.body) { | ||
| return 'body'; | ||
| } | ||
| const dialog = active.closest('dialog'); | ||
| return dialog == null | ||
| ? 'page' | ||
| : dialog.hasAttribute('inert') | ||
| ? 'retained' | ||
| : 'active'; | ||
| }); | ||
| expect(where, `after ${index + 1} Tab presses`).not.toBe('page'); | ||
| expect(where, `after ${index + 1} Tab presses`).not.toBe('retained'); |
There was a problem hiding this comment.
This loop starts 900 ms after Continue, but the retained dialog closes 644-689 ms after Continue in both Chromium and WebKit, so no retained dialog is open during any Tab press. Even mid-handoff a control inside the retained dialog cannot take focus, and if the inert attribute were missing this classifier would report it as active, so not.toBe('retained') can never fail. Consider pressing Tab right after toHaveCount(2) and asserting that focus is inside the dialog labelled "Notification frequency" (or on <body>), or drop "never the retained dialog" from the header (lines 9-10).
7e0b6e9 to
a60dd43
Compare
e161852 to
62e0ac1
Compare
There was a problem hiding this comment.
Approval of this evidence PR waits on a maintainer decision: the behavior its four browser tests pin as expected (each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, and a CSS page-scroll lock with no <body> styles) is governed only by component:BottomSheetSwitcher DEC-1/FR2/FR8 and component:BottomSheet FR5/DEC-2, which are still draft, so a maintainer needs to approve or reject those records as proposed in #7744. The Tab test now presses Tab during the handoff, but its last inert/aria-hidden check runs within about 170 ms of Continue while the retained dialog stays open, and fades, until about 650 ms, so a regression that drops either attribute during the fade still passes even though the header says "throughout" (line 12). Please keep checking until the retained dialog closes, reading the open count and both attributes in one page.evaluate so the close cannot race a toHaveAttribute retry, or narrow the header, the in-test comment and the description to the start of the handoff. All four tests pass in Chromium and WebKit.
[Automated review]
| for (let index = 0; index < 4; index += 1) { | ||
| await page.keyboard.press('Tab'); | ||
| const openCount = await page.locator('dialog[open]').count(); | ||
| if (openCount < 2) { | ||
| break; | ||
| } | ||
| midHandoffPresses += 1; | ||
| const where = await focusLocation(page); | ||
| expect(where, `mid-handoff, after ${index + 1} Tab presses`).not.toBe( | ||
| 'page', | ||
| ); | ||
| expect(where, `mid-handoff, after ${index + 1} Tab presses`).not.toBe( | ||
| OPENER, | ||
| ); | ||
| expect(['Notification frequency', 'body']).toContain(where); | ||
| await expect(retained).toHaveAttribute('inert', ''); | ||
| await expect(retained).toHaveAttribute('aria-hidden', 'true'); | ||
| } |
There was a problem hiding this comment.
These four presses all land within the first ~75-170 ms after Continue in both Chromium and WebKit, but the retained dialog stays open until ~645-695 ms and starts fading at ~345 ms, so the inert/aria-hidden checks never see the fade: removing both attributes once the retained panel starts fading leaves this test green in both engines. Line 12 says "throughout" and lines 143-147 say "for as long as it is open". Either keep stepping until the retained dialog closes, reading its state in one call so the close cannot race a toHaveAttribute retry, for example:
for (let step = 0; step < 40; step += 1) {
await page.keyboard.press('Tab');
const state = await page.evaluate(name => {
const retained = Array.from(document.querySelectorAll('dialog')).find(
dialog => dialog.getAttribute('aria-label') === name,
);
const active = document.activeElement;
const host = active?.closest('dialog');
return {
open: retained?.open ?? false,
inert: retained?.hasAttribute('inert') ?? false,
hidden: retained?.getAttribute('aria-hidden') ?? null,
where:
active == null || active === document.body
? 'body'
: host == null
? 'page'
: host.getAttribute('aria-label'),
};
}, OPENER);
if (!state.open) {
break;
}
midHandoffPresses += 1;
expect(state.inert, `step ${step + 1}`).toBe(true);
expect(state.hidden, `step ${step + 1}`).toBe('true');
expect(['Notification frequency', 'body']).toContain(state.where);
await page.waitForTimeout(25);
}or narrow line 12 and the comment to the start of the handoff.
a60dd43 to
adb3070
Compare
62e0ac1 to
69a5625
Compare
There was a problem hiding this comment.
Approval of this evidence PR waits on a maintainer decision: the behavior its tests commit as expected (each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, and a CSS page-scroll lock with no <body> styles and its gutter padding fallback) is governed only by component:BottomSheetSwitcher DEC-1/FR2/FR8 and component:BottomSheet FR5/DEC-2, which are still draft, so a maintainer needs to approve or reject those records as proposed in #7744. The Tab test now covers the retained dialog's fade, but its focus checks (lines 194 and 211) accept <body> on every press, so it still passes when focus can never enter the entering sheet, although its title says Tab stays inside it. Please require at least one Tab stop inside "Notification frequency" during the handoff and one after it, or narrow the title, header and comment to "never the page". All four browser tests pass in Chromium and WebKit, and the new unit case passes.
[Automated review]
| const label = `mid-handoff, Tab ${index + 1} at ${Date.now() - started}ms`; | ||
| expect(step.where, label).not.toBe('page'); | ||
| expect(step.where, label).not.toBe(OPENER); | ||
| expect(['Notification frequency', 'body'], label).toContain(step.where); |
There was a problem hiding this comment.
This accepts body on every press, as does line 211, so the test stays green when the entering sheet can never take focus: with every open sheet dialog made inert from the start of the handoff, it passes 3 of 3 in Chromium and in WebKit with all 21 reads body, while unmutated runs land in "Notification frequency" on 10-11 of the 13 mid-handoff reads and 6-7 of the 8 later ones. Counting those reads and requiring at least one per loop would make the title's "Tab stays inside the entering sheet" something the test proves:
let insideMid = 0;
// in the loop, after the checks above
if (step.where === 'Notification frequency') insideMid += 1;
// after the loop
expect(insideMid, 'Tab reached the entering sheet mid-handoff').toBeGreaterThan(0);The same count after the handoff covers line 211; otherwise narrow the title, header and comment to "never the page".
69a5625 to
88f9927
Compare
adb3070 to
1221b3b
Compare
There was a problem hiding this comment.
Approval of this evidence PR waits on a maintainer decision: the behavior its browser tests commit as expected (each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, and one page scroll lock held for the whole flow) is governed only by component:BottomSheetSwitcher DEC-1 with FR2, FR5 and FR8, which are still draft, so a maintainer needs to approve or reject them as proposed in #7744. Separately, the probe dialog in the scroll-lock test (lines 185-206) is a raw showModal() element that never takes Core's shared lock, so the test still passes when a nested holder's release unpins the page; please open a real Core Dialog from a step and assert the pin after it closes, or reword the comment, title and header to what the probe shows. The Tab test's new stop counts close the earlier focus-landing gap, and all four tests pass in Chromium and WebKit.
[Automated review]
| // A probe showModal() dialog above the step shares the lock's counter; | ||
| // its close gives back only its own hold. |
There was a problem hiding this comment.
This probe is a raw <dialog> the test opens itself, and Core's counter only moves when a component calls useScrollLock (packages/core/src/hooks/useScrollLock.ts, lines 33 and 53-109, which observe no DOM), so the probe never takes a hold and its close gives nothing back. With the hook's release guard reduced to originalBodyState == null, so that any holder's release unpins the page, this test still passes 3 of 3 in Chromium and in WebKit, while that mutant would unpin the page when a Dialog opened from a step closes (Dialog.tsx line 586 holds the same lock). Consider opening a Core Dialog from a step and asserting position, top and the scroll counter after it closes, or narrowing this comment, the title (line 154) and the header (lines 9-10) to what the probe shows: an unrelated native modal dialog opening and closing above a step leaves the lock alone.
88f9927 to
86af9ad
Compare
1221b3b to
6a02977
Compare
There was a problem hiding this comment.
Approval of this evidence PR waits on a maintainer decision: the behavior its browser tests commit as expected (each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, and one page scroll lock held for the whole flow, including beneath a Dialog opened from a step) is governed only by component:BottomSheetSwitcher DEC-1 with FR2, FR5 and FR8, which are still draft, so a maintainer needs to approve or reject them as proposed in #7744. The scroll-lock test now opens a real Core Dialog from the new "Dialog from a step" story and fails in Chromium and WebKit when a nested holder's release unpins the page, which resolves the earlier probe-dialog finding, and all four tests pass in both engines. Optionally, keep the story's Section as the last child of the sheet's content: the closed Dialog rendered after it removes the Section's edge compensation, so that frequency step renders 16 px taller than in "Hug content".
[Automated review]
| </HStack> | ||
| </VStack> | ||
| </Section> | ||
| {hasResetDialog ? ( |
There was a problem hiding this comment.
Optional polish: this closed Dialog renders after the Section, so the Section is no longer the last child of the sheet's content and loses its :last-child block-end compensation (packages/core/src/Section/Section.tsx line 76). In this story the frequency step therefore renders 16 px taller than in "Hug content": the Section computes margin-block-end: 0px instead of -16px, and the content area is 265 px tall instead of 249 px. Keeping the Section as the last child would give the step the same geometry as the other stories; nothing the lock test asserts depends on it.
6a02977 to
7a31451
Compare
86af9ad to
0da8423
Compare
There was a problem hiding this comment.
Approval of this evidence PR needs a maintainer to approve the spec that governs what its browser tests expect: each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, and one page scroll lock held for the whole flow, including beneath a Dialog opened from a step, are decided only by component:BottomSheetSwitcher DEC-1 with FR2, FR5 and FR8, which are still draft in #7744. All four tests pass in Chromium and WebKit against a Storybook built from this branch and fail when the shared lock is released by a nested holder or the retained dialog stops being inert as it fades, and the “Dialog from a step” story now keeps the frequency step’s geometry. Nothing else is needed unless the maintainer amends those requirements, in which case the tests’ expectations should follow.
[Automated review]
7a31451 to
b77bd3e
Compare
0da8423 to
bf0478f
Compare
There was a problem hiding this comment.
Approval of this evidence PR waits on a maintainer decision: what its tests encode (each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, and one page scroll lock held through a handoff and beneath a Dialog opened from a step) is decided only by component:BottomSheetSwitcher DEC-1 with FR2, FR5, FR8 and PR1, which landed on main with #7744 but are still authority: draft with approved_by: null. A maintainer needs to make those claims current as written, or amend them, in which case the tests' expectations should follow. All four browser tests pass in Chromium and WebKit against a Storybook built from this branch, and they fail as they should when a nested lock holder's release unpins the page or the retained dialog stops being inert as it fades; the inline note on the Tab test's 400 ms late-press check is optional hardening.
[Automated review]
| // stopped inside the entering sheet, else an entering sheet that can never | ||
| // take focus (every stop on the browser-UI `body` stop) would pass. | ||
| expect(midHandoffPresses).toBeGreaterThan(2); | ||
| expect(lateHandoffPresses).toBeGreaterThan(0); |
There was a problem hiding this comment.
Optional hardening: this check failed once in 16 Chromium runs against a Storybook built from this branch, in a run where each test took two and a half to five times its usual duration; the other runs, and both WebKit runs, passed. The late window starts 400 ms after started, which is taken after the toHaveCount(2) wait and a page read (about 40–60 ms after the Continue click), and ends when the retained dialog closes (about 670–710 ms after the click), so it holds only four to five presses at the usual ~48 ms per press. Counting a press as late when the retained panel is actually fading (for example its computed opacity below 1, read in handoffStep) would tie the check to the fade it means instead of to wall-clock time.
56c27f5 to
271bec8
Compare
f1cb56b to
e6c150b
Compare
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewNo new or modified components detected. Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Visual Regression46 of 494 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 26 more. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
There was a problem hiding this comment.
Approval of this evidence PR waits on a maintainer decision: what its tests encode (each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, one page scroll lock held through a handoff and beneath a Dialog opened from a step, and the switcher's id and data-testid on the host dialog) is decided only by component:BottomSheetSwitcher DEC-1 and DEC-2 with FR2, FR5, FR8, FR9 and PR1, which are still authority: draft with approved_by: null on main. A maintainer needs to make those claims current as written, for example by landing #7822, or amend them, in which case the tests' expectations should follow. All four browser tests pass in Chromium and WebKit against a Storybook built from this branch, they fail as they should when a nested lock holder's release unpins the page or the retained dialog stops being inert as it fades, and the Tab test's late window now holds under load.
[Automated review]
271bec8 to
478616c
Compare
e6c150b to
a7d9e09
Compare
There was a problem hiding this comment.
Approval of this evidence PR waits on a maintainer decision: what its tests encode (each BottomSheetSwitcher step in its own native modal dialog, one scrim handed over without a fade, one page scroll lock held through a handoff and beneath a Dialog opened from a step, and the switcher's id and data-testid on the host dialog) rests on component:BottomSheetSwitcher DEC-1 and DEC-2 with FR2, FR5, FR8, FR9 and PR1, which are still authority: draft with approved_by: null on main. The promotion in #7822 now has an engineering owner's approval; once it lands with those claims current as written, and #7720 beneath this PR lands, nothing else here blocks, and if the claims are amended instead, the tests' expectations should follow. All four browser tests pass in Chromium and WebKit against a Storybook built from this branch, and they fail as they should when a nested lock holder's release unpins the page or the retained dialog stops being inert as it fades.
[Automated review]
a7d9e09 to
4587172
Compare
478616c to
e43f5a8
Compare
4587172 to
f701ca6
Compare
e43f5a8 to
991e1df
Compare
There was a problem hiding this comment.
One small change is needed before this can be approved: because the PR edits two spec records (BottomSheet.spec.md and BottomSheetSwitcher.spec.md), its description needs a ## Writing rubric results section with one line per rubric item R1–R6 from docs/contributing/spec-writing.md. Everything the tests expect is current spec on main since #7822 landed, and all four browser tests pass in Chromium and WebKit against a Storybook built from this branch and fail as they should when the lock or the retained dialog's inertness is broken. No code changes are needed.
[Automated review]
| | Contract | Verification | Representative states | Mutation or failure expectation | Audit section | | ||
| | ---------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | | ||
| | 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` | |
There was a problem hiding this comment.
This edit, together with the verified_by entries in both records, makes this a record-changing pull request, so the description needs a ## Writing rubric results section with one line per rubric item R1–R6 (docs/contributing/spec-writing.md, required by architecture:knowledge-contracts). The changed lines themselves carry no pull request reference or narration.
There was a problem hiding this comment.
This is ready to approve: everything the new browser and jsdom tests expect is stated by component:BottomSheetSwitcher, current on main, and the two record edits only list the new test file. All four browser tests pass in Chromium and WebKit against a Storybook built from this branch, and they fail as they should when a nested lock holder's release unpins the page or the retained dialog stops being inert as it fades. The ## Writing rubric results section requested earlier is now in the description, so nothing more is needed from you; this reaches main together with #7720 beneath it.
[Automated review]
Adds the Chromium and WebKit spec for a flow whose every step owns a native modal dialog: the flow's one page scroll lock (the shared hook) holding through a handoff and under a Core Dialog opened from a step, never released between steps nor when that Dialog closes, with the offset restored after the exit; focus inside the entering sheet as soon as both dialogs are open and on Tab stops for as long as the retained dialog is open (inert and hidden through its fade), inside the flow after; Escape and backdrop dismissal with focus returned to the opener; and one steady scrim through a handoff. Adds the 'Dialog from a step' switcher story the lock test drives, the unit case for the lock under a Core Dialog opened from a step, the EdgeTint case for one tint per presented dialog through a handoff, the reduced-motion scrim-pin case, and the server-markup cases. The verification map no longer marks the engine spec as missing from the tree.
f701ca6 to
8a7e375
Compare
|
/approve-spec 8a7e375 |
There was a problem hiding this comment.
This is ready to approve: everything the new browser and jsdom tests expect is stated by component:BottomSheetSwitcher, current on main, and the two record edits only list the new test file. All four browser tests pass in Chromium and WebKit against a Storybook built from this branch, and they fail as they should when a nested scroll-lock holder's release unpins the page or the retained dialog stops being inert as it fades. Nothing more is needed from you.
[Automated review]
Primary intent
Commit the cross-browser proof for a
BottomSheetSwitcherflow whose every step owns a native modal dialog:__tests__/BottomSheetNativeModal.a11y.browser.spec.ts(Chromium + WebKit) — the flow's one page scroll lock (the shareduseScrollLock, asDialog) holding with two open sheet dialogs through a handoff and under a CoreDialogopened from a step, never released between steps nor when thatDialogcloses, with the page's offset restored after the exit; focus inside the entering sheet's dialog as soon as both dialogs are open, and Tab stops inside it for as long as the retained dialog is open beneath it, its fade included (and never on the page after the handoff), with the retained dialoginertandaria-hiddenon every step it is open; Escape and a backdrop tap dismissing an info flow with focus returned to the opener; one steady scrim through a handoff (no dip, no double dim).User or maintainer impact
None at runtime. Maintainers get the engine evidence
component:BottomSheetSwitcherFR5–FR7 and AR1–AR3 name in their verification rows.Problem and solution fit
<body>position: fixed; top: -300pxfrom open through the two-dialog handoff; wheel (both engines) and a CDP touch drag (Chromium) over the backdrop leavescrollYat 0 and the pin unchanged; a windowscrollcounter reads 0 across the handoff and across a real CoreDialogopened from the step (the new "Dialog from a step" story's Reset → "Reset frequency?" dialog,purpose="form") and closed with Keep — the pin andtop: -300pxsurvive its close (a lock released and re-acquired, or a nested holder's release unpinning the page, would restore the offset and fire one); after Escape the pin is gone,scrollYis 300, and a wheel scrolls the page againshowModal()probe, which never takes Core's shared lock, so it could not catch a nested holder's release unpinning the page. Mutant:useScrollLock's release guard reduced tooriginalBodyState == null(any holder's release unpins) — fails in Chromium and WebKit onposition""≠fixedright after theDialogcloses; the real hook passes 3 of 3 repeats in each engineBottomSheetNativeModal.a11y.browser.spec.ts(4 tests,.a11y.browser.spec.tsso the WebKit project runs it)toHaveCount(2)until the retained dialog closes (~650ms), reading the open count, focus location, and the retained dialog'sopen/inert/aria-hiddenin onepage.evaluateper step so a close cannot race a retry; asserts focus is inside "Notification frequency" (or on<body>, the browser-UI stop native containment adds) and never in "Set up notifications" or the page; assertsinertandaria-hidden="true"on every step the retained dialog is open; requires more than two mid-handoff presses and at least one after 400ms (inside the fade, which starts ~345ms); requires at least one Tab stop inside "Notification frequency" during the handoff and at least one after it, where a stop is a press that moved focus onto an element outside any inert subtree (body, the browser-UI stop native containment adds per lap, stays allowed but does not count); then 8 presses after the handofffading/exitingfrom the sheet'sisInactive(attributes vanish once the fade starts) fails in Chromium at 344ms and in WebKit at 355ms onretained dialog inert; the fixed build passes 3 of 3 repeats in each engine. Focus itself cannot enter a lower modal dialog in any engine, which is why the attributes are asserted directly. Second mutant: every sheet dialoginertfrom the start of the handoff on — fails in Chromium and WebKit onmidHandoffStopsInside > 0. Third (eighth round): the test now reads focus the moment both dialogs are open, before any Tab, and requires it inside "Notification frequency" and outside every inert subtree (true on this branch, both engines, 3 of 3 repeats); an entering sheet madeinertwhile it enters — focus left on<body>for its first ~300ms — fails in Chromium and WebKit on that first read. Fourth (tenth round): at least one Tab stop inside "Notification frequency" must land after 400ms, inside the retained dialog's fade, so the "its fade included" claim is tested; an entering sheet madeinertonly while the retained one fades fails in Chromium and WebKit on that count (lateHandoffStopsInside); the real build passes 4 of 4 repeats in each engineRecord edit in this PR, exactly:
BottomSheetSwitcher.spec.md's verification map drops "(implementation gap: not yet in the tree)" afterBottomSheetNativeModal.a11y.browser.spec.ts, because this PR adds that file. Nothing else in either record changes.BottomSheetSwitcher.test.tsxgains "keeps the lock engaged under a CoreDialogopened from a step, and theDialogpaints above the step" with itsDialogInStepFlowfixture (that fixture renders theDialoginside the step's content; the story renders it as a sibling — both are valid shapes for FR5, so this is a unit companion rather than a literal twin), and "forces alertdialog on a required step and leaves the other sheet dialogs with no role when the switcher sets none";BottomSheetEdgeTint.test.tsxgains "gives a modal switcher flow exactly one tint at rest, and one per presented dialog through a handoff".BottomSheetSwitcher.test.tsxalso gains "keeps the scrim takeover pin under prefers-reduced-motion" (the compiled-stylesheet check) and the two server-markup cases ("renders the switcher id and data-testid once, on the active sheet's dialog, in the server markup of a flow that mounts open"; "renders every sheet dialog closed in server markup, with the switcher id on no element until the first commit", withhydrateRoot). All six moved here from #7720 to keep that PR under the size threshold.Tab test hardening (eleventh round). The 400 ms "late" window was measured from a runner-side timestamp taken after the second dialog had been observed open, so on a slow run it started late and could end after the retained dialog closed (1 failure in 16 slow Chromium runs). A
MutationObserverinstalled before the Continue click now stampsperformance.now()on the page the moment the second dialog opens, and every step reads its elapsed time from that stamp in the samepage.evaluateas the focus and attribute reads; the first read asserts the stamp exists. 32 of 32 (4 tests × 4 repeats × Chromium and WebKit); the fade-only-inert mutant still fails in both engines onlateHandoffStopsInside.Merge-order note (no change here). #7648 (open-order stacking) deprecates
BottomSheetSwitcherand retitles its stories "(deprecated)". If #7648 lands, the "Dialog from a step" story this PR adds becomes one more copyable switcher example in the deprecated stories; whichever direction the owner picks for the switcher decides whether this story should then be removed or kept as the deprecated component's evidence. No package source, documentation, or record changes. The one non-test file isapps/storybook/stories/BottomSheetSwitcher.stories.tsx: a "Dialog from a step" story (the frequency step gains a Reset control that confirms through a CoreDialog), which the lock test drives and which the record'sverified_byalready lists. TheDialogis a sibling of the step'sBottomSheet(a fragment), not sheet content, so the sheet keeps its lone-Sectioncontent geometry: the frequency step's panel measures 302px in both "Hug content" and "Dialog from a step" (a closed<dialog>inside the content, even placed before theSection, made it 318px). Removable tagalongs: none.Authority
component:BottomSheetSwitcherDEC-1 (spec(core): each BottomSheetSwitcher step owns its native dialog #7744) — the structure this evidence proves; its verification map names this spec file.spec:AST-013FR10 — browser-owned behaviour (modal containment, top-layer order, backdrop paint) gets real-engine evidence here, in Chromium and WebKit.Change and evidence
Browser (Chromium 149 and WebKit under Playwright 1.61, static Storybook of this branch). The committed spec: 42 passed in Chromium (38 pre-existing + these 4), 34 passed in WebKit (30 + 4); the whole file 3 of 3 repeats in Chromium and 4 of 4 in WebKit. Re-measured on this build against
main's in both engines (dim per frame normal and reduced motion, focus after each switch and dismissal, Tab stops, the lock's pin and offset through a handoff and after the exit): identical tomain's readings apart from the browser-UIbodyTab stop native containment adds; 0 console errors or warnings.Writing rubric results
This PR edits two knowledge records, only where the browser spec it adds is named:
verified_byincomponent:BottomSheetandcomponent:BottomSheetSwitcherlists__tests__/BottomSheetNativeModal.a11y.browser.spec.tsagain, and the switcher's verification-map row for FR5–FR7 and AR1–AR3 drops its "(implementation gap: not yet in the tree)" note, so the row names the file as it now exists. The rest of that table's diff is column realignment from the shorter cell; no other cell changes.verified_byentries name the file as present, with no history or "now".#+ digits,/pull/,/issues/).Scope
Testing