Skip to content

docs(spec): evolve BottomSheetSwitcher with an ordered sheet path - #6042

Open
imdreamrunner wants to merge 2 commits into
mainfrom
spec-bottom-sheet-stack
Open

imdreamrunner wants to merge 2 commits into
mainfrom
spec-bottom-sheet-stack

Conversation

@imdreamrunner

@imdreamrunner imdreamrunner commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Why

BottomSheet can present one panel and BottomSheetSwitcher can replace one active step with another, but neither represents a visible multi-level drill-in path. The motivating case is a sheet opened by the switcher that itself needs to open a stacked sheet: a separate stack coordinator cannot express that, because the base sheet would belong to neither path and Back, Close-all, focus, and lifetime would split across two state machines.

This PR proposes that contract before the prototype becomes a released API.

What

Revises draft system spec spec:AST-044 to evolve BottomSheetSwitcher in place — no new BottomSheetStack component:

  • one canonical controlled activeSheets value ordered bottom-to-top, with stable child sheetId values within a controller-local scope;
  • the released activeSheet / onActiveSheetChange props remain as a deprecated length ≤ 1 projection of the same list (null ⇔ []), so existing consumers work unchanged; supplying both forms warns in development and the list form wins;
  • append for push, suffix removal for Back, suffix replacement for branch changes, and [] for Close all — a switcher-opened sheet stacks by appending to its own host's list;
  • onActiveSheetsChange(next, {reason, dismissedSheetId}) for component-owned dismissal; the legacy callback receives next.at(-1) ?? null;
  • one shared host, modal boundary, scrim, scroll lock, focus boundary, and dismissal owner;
  • covered sheets remain mounted but inert and accessibility-hidden;
  • released hasScrim presentation retained at every path length (no new modality prop), plus a new finalFocusRef root policy;
  • one-level implicit dismissal based on the top sheet's purpose;
  • longest-valid-prefix recovery for unknown, duplicate, or dynamically removed ids;
  • shared dismissal, layer-depth, branch, and same-gesture coordination through the existing family and accepted AST-003 contracts; and
  • explicit SSR, rapid-update, reduced-motion, performance, browser, accessibility, and verification requirements.

Silk (@silk-hq/components) was evaluated and rejected as the model: it has no coordinator or declared order — each sheet owns an independent presented boolean, front/covered is derived from presentation chronology (simultaneous presentation is unspecified), and SheetStack only groups sheets for cumulative-progress stacking animation.

Candidate API

const [activeSheets, setActiveSheets] =
  useState<ReadonlyArray<string>>([]);

<BottomSheetSwitcher
  activeSheets={activeSheets}
  onActiveSheetsChange={setActiveSheets}
  hasScrim={false}
  finalFocusRef={openerRef}>
  <BottomSheet sheetId="issues" label="Issues">
    …
  </BottomSheet>
  <BottomSheet sheetId="details" label="Issue details">
    …
  </BottomSheet>
</BottomSheetSwitcher>;

// Legacy form, unchanged: equivalent to activeSheets = sheet == null ? [] : [sheet]
<BottomSheetSwitcher activeSheet={sheet} onActiveSheetChange={setSheet}>
  …
</BottomSheetSwitcher>;

The path and final destination are caller-owned intent; presentation reuses the released hasScrim contract unchanged. Exact recede geometry and any public theme seam remain a design/theming decision rather than entering the API as arbitrary animation callbacks.

Proposed decisions

  1. Extend the switcher with one explicit ordered path rather than adding a new component, imperative navigation, or inferred sibling chronology.
  2. Retain released hasScrim presentation (no modality prop) and add finalFocusRef as the only new root policy.
  3. Treat Escape, platform close, visible modal-scrim activation, and allowed swipe as one-level Back.
  4. Keep covered panels mounted under one opinionated host.
  5. Recover from invalid ids with the longest valid unique prefix without rewriting caller state.

All remain proposed: the record is authority: draft / phase: proposed.

Open questions

  • Ratify the dual-prop switcher API (activeSheets canonical, activeSheet legacy alias) and lack of imperative navigation.
  • Ratify retaining released hasScrim presentation with no modality prop, adding only finalFocusRef.
  • Confirm one-level implicit dismissal rather than Close all.
  • Confirm invalid-path recovery without a normalization callback.
  • Decide exact recede geometry, visible depth, reduced motion, and whether any theme seam is public.

Relationship to the prototype

PR #5813 remains implementation evidence only. It demonstrates the ordered path and shared-host mechanics, but still couples scrim and modality and does not settle the hardened invalid-id, shared-dismissal, focus, SSR, or rapid-update contract in this spec (renumbered AST-044 after AST-026 was taken on main by the shadcn Registry spec, which caused the merge conflict — now resolved by rebase). Under the revised proposal its host is re-based as BottomSheetSwitcher, not a new component.

Risk

Knowledge only. No Core runtime, public package, generated artifact, visual output, or consumer behavior changes. The spec does not authorize implementation while it remains draft. No Changeset.

Validation

  • pnpm check:knowledge -- --base origin/main
  • pnpm lint:strict — 0 errors; 90 existing repository warnings
  • Prettier, git diff --check, contiguous contract-id check, and public-content scan
  • Two independent contract reviews; all blocker/high findings reconciled against AST-002, AST-003, architecture:public-component-api, architecture:layer-runtime, family:overlay-dismissal, and current BottomSheet/BottomSheetSwitcher source
  • Revision to the switcher model and removal of the modality prop: check:knowledge error count identical before/after (42 pre-existing environment errors, none for this spec)

@vercel

vercel Bot commented Sep 4, 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 Sep 26, 2026 2:43am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Sep 4, 2026
@imdreamrunner
imdreamrunner marked this pull request as ready for review September 4, 2026 20:31
@github-actions github-actions Bot added the spec-auto-merge Auto-merge was enabled by the spec owner gate label Sep 4, 2026
@astracat-bot

astracat-bot Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Sep 26, 2026, 2:48 AM UTC

@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 is waiting on a maintainer decision for this proposal.

This pull request proposes a new BottomSheetStack contract — a controlled ordered sheet-id path for multi-level drill-in flows, with independent modality, scrim, and final-focus policy, one-level dismissal, and longest-valid-prefix recovery — but that contract is still a draft proposal with its main decisions explicitly open. Approval needs a maintainer to ratify this record as the governing spec, or identify a different governing spec, settling at least:

  • the ordered-id API and lack of imperative navigation;
  • independent modality, hasScrim, and finalFocusRef policy;
  • one-level implicit dismissal rather than Close all, and invalid-path recovery without a normalization callback;
  • before release, recede geometry and any public theme seam, and whether live modality changes are supported or latched.

One separate fix is also needed regardless of that decision: as written, omitting hasScrim derives scrim paint from modality, so changing modality alone would also change page dimming — contradicting the proposal's own claim that the two inputs are independent. Please give hasScrim a fixed modality-independent default, or explicitly model an auto policy and revise the independence wording to match.

No runtime or consumer behaviour changes in this pull request today.

[Automated review]

@imdreamrunner imdreamrunner changed the title docs(spec): propose BottomSheetStack contract docs(spec): evolve BottomSheetSwitcher with an ordered sheet path Sep 25, 2026

@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 is waiting on a maintainer decision for this proposal.

This pull request revises the draft spec to evolve BottomSheetSwitcher in place — a canonical controlled activeSheets ordered path for multi-level drill-in flows, with the released activeSheet props kept as a length ≤ 1 alias, independent modality, scrim, and final-focus policy, one-level dismissal, and longest-valid-prefix recovery — but that contract is still a draft proposal with its main decisions explicitly open. Approval needs a maintainer to ratify this record as the governing spec, or identify a different governing spec, settling at least:

  • the dual-prop ordered-path API and lack of imperative navigation or a new component;
  • independent modality, hasScrim, and finalFocusRef policy;
  • one-level implicit dismissal rather than Close all, and invalid-path recovery without a normalization callback;
  • before release, recede geometry and any public theme seam, and whether live modality changes are supported or latched.

Two separate fixes are also needed regardless of that decision:

  • As written, omitting hasScrim derives scrim paint from modality, so changing modality alone would also change page dimming — contradicting the proposal's own claim that the two inputs are independent. Please give hasScrim a fixed modality-independent default, or explicitly model an auto policy and revise the independence wording to match.
  • The promised compatibility for existing callers is not delivered by the operative rules: today hasScrim={false} selects a non-modal host, but under the draft's unconditional modality default and paint-only hasScrim, that caller would instead get a modal host without a scrim. Please either preserve the released meaning when modality is not supplied, or treat the reinterpretation as an explicit compatibility change with a migration and update the "identical behavior" wording.

The record also needs renumbering on rebase, as its spec id is now taken by a different record on main — as the pull request description already notes.

No runtime or consumer behaviour changes in this pull request today.

[Automated review]

@imdreamrunner

Copy link
Copy Markdown
Contributor Author

Updated in 15a9fd6 per the automated review's two findings: the proposed modality prop is removed entirely.

  • No shipped Astryx component exposes a modality prop and no consumer in this proposal requires modal-without-scrim or non-modal-with-dimming; the prop had been carried over from the Drawer exploration without a need case.
  • The switcher now retains released hasScrim presentation at every path length (FR7-FR9, FR38, FR41), and DEC-2 explicitly rejects an independent modality prop in this change — recording the AST-002/FR16 coupling as deferred debt for a future family-wide decision.
  • This resolves both findings at once: there is no derived hasScrim default to contradict independence, and existing hasScrim={false} callers keep identical behavior. OQ6 (live modality changes) is removed; open questions are now OQ1-OQ5.

…path

Supersedes the AST-026 draft in this PR: main has since assigned AST-026
to the shadcn Registry spec, causing the merge conflict. Same content,
renumbered to the next free id AST-044 (schema_version 4), in
docs/specs/AST-044-bottom-sheet-switcher-path/.

Proposal: canonical activeSheets list on BottomSheetSwitcher with the
released activeSheet as a length<=1 alias, retained hasScrim
presentation (no modality prop), one-level dismissal, longest-valid-
prefix recovery.
@github-actions

github-actions Bot commented Sep 25, 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 is waiting on a maintainer decision for this proposal.

This pull request proposes evolving BottomSheetSwitcher in place — a canonical controlled activeSheets ordered path for multi-level drill-in flows, with the released activeSheet props kept as a length ≤ 1 alias, released hasScrim presentation retained with no modality prop, a new finalFocusRef policy, one-level dismissal, and longest-valid-prefix recovery — but that contract is still a draft proposal with its main decisions explicitly open. Approval needs a maintainer to ratify this record as the governing spec, or identify a different governing spec, settling at least:

  • the dual-prop ordered-path API and lack of imperative navigation or a new component;
  • retaining released hasScrim presentation with no modality prop, adding only finalFocusRef;
  • one-level implicit dismissal rather than Close all, and invalid-path recovery without a normalization callback;
  • before release, recede geometry and any public theme seam.

One separate fix is also needed regardless of that decision: the draft still contains wording from the earlier split paint/modality model that contradicts the presentation model it now adopts. IR4 says hasScrim must not be used as the source of truth for presentation, while FR8, FR11, and FR38 make hasScrim own presentation and modal semantics. Please rewrite IR4 to match that retained coupling, and align the related contract-gate sentence, the FR33–FR39 verification expectation, and the need-gate count (it says four cases but lists three).

No runtime or consumer behaviour changes in this pull request today.

[Automated review]

…crim

IR4 now makes hasScrim the source of truth for presentation, the
contract-gate sentence and FR33-FR39 verification match FR8/FR11/FR38,
and the need gate correctly counts three cases.

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This draft specification is ready to approve.

It proposes evolving BottomSheetSwitcher in place — no new component — with a canonical controlled activeSheets ordered path for multi-level drill-in flows, the released activeSheet props kept as a length ≤ 1 alias, released hasScrim presentation retained at every path length with no modality prop, a new finalFocusRef policy, one-level dismissal, and longest-valid-prefix recovery. The maintainer approval recorded on this pull request ratifies those decisions, so the direction that was previously waiting on a maintainer is now settled for this draft record.

The wording problem from the previous revision is also fixed: the implementation rule now consistently treats hasScrim as the source of truth for presentation, matching the presentation, contract-gate, and verification sections, and the need-gate count matches the cases it lists.

One item remains open by design, and it does not block this draft: exact recede geometry and whether any theme seam is public must be settled before release, along with the implementation and real-browser evidence the record requires before shipping.

No runtime or consumer behaviour changes in this pull request today.

[Automated review]

@cixzhang cixzhang reopened this Oct 30, 2026

This branch was successfully deployed

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

Labels

CLA Signed This label is managed by the Meta Open Source bot. 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.

2 participants