diff --git a/.changeset/bright-shadows-plan.md b/.changeset/bright-shadows-plan.md new file mode 100644 index 00000000..a845151c --- /dev/null +++ b/.changeset/bright-shadows-plan.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/docs/adr/0007-prototype-shadow-dom-style-isolation.md b/docs/adr/0007-prototype-shadow-dom-style-isolation.md index ccec539d..092d3312 100644 --- a/docs/adr/0007-prototype-shadow-dom-style-isolation.md +++ b/docs/adr/0007-prototype-shadow-dom-style-isolation.md @@ -1,6 +1,19 @@ # ADR 0007: Prototype automatic Shadow DOM style isolation -Status: Proposed (validated experimentally; not approved for production rollout) +Status: Accepted for coordinated rollout planning; production expansion is not yet shipped + +YPE-5356 accepts the architecture for a coordinated major-version rollout +across the compatible public UI boundary defined in the +[production rollout policy](../shadow-dom-rollout-policy.md). Implementation is +split into dependency-ordered component groups, but the package must not publish +a partial boundary. This ADR continues to describe current runtime behavior +until those groups land: only `YouVersionAuthButton` creates an automatic shadow +root. + +The automatic boundary belongs to the SDK-owned top-level component instance. +Compound members and SDK components composed inside another isolated SDK +component reuse their owning boundary instead of creating accidental nested +roots. Excluded exports and their reasons are defined in the rollout policy. Host applications can apply unlayered global CSS, including Tailwind preflight, that outranks the UI package's layered styles. Resets, stronger selectors, @@ -119,11 +132,12 @@ available after the shadow content mounts and points to the real component element inside the shadow root, not to the light-DOM host. Before that mount, the component cannot receive focus or interaction. -This contract must be accepted separately for every component selected for -automatic isolation. A component that requires server content, no-JavaScript -content, or a stable first-paint footprint cannot use this host unchanged. -YPE-5356 owns that rollout policy, including whether a component needs reserved -space, a product timing budget, or a different SSR strategy. +This contract is reviewed separately for every implementation group selected +for automatic isolation. YPE-5356 accepts it as the shared starting point, but a +component that requires server content, no-JavaScript content, or a stable +first-paint footprint cannot use this host unchanged. Its implementation ticket +must exclude it or define reserved space, a product timing budget, or a +separately approved SSR strategy. ## Consequences @@ -154,9 +168,11 @@ that closes and reopens during retained exit presence also preserves and restore its original opener; disconnected targets and targets moved out of their captured root (into the light DOM, another shadow root, or another document) are ignored. -These observations do not select or design production overlay coordination. -YPE-5356 owns deciding whether and how to support concurrent peers. The detailed -Chromium evidence and remaining validation live in the rollout plan. +YPE-5356 accepts single-active-peer popover dismissal rather than introducing +cross-root overlay coordination without a demonstrated product journey. The +focus-restoration defects found by YPE-5355 were resolved by YPE-5889/PR 414 and +remain covered as regressions. The detailed cross-browser evidence and remaining +validation live in the rollout plan. Radix's development-only relationship checks can also emit warnings for valid IDs inside a shadow root because those checks query the document rather than @@ -165,8 +181,11 @@ the root. Only `YouVersionAuthButton` is automatically isolated by this prototype. `BibleVersionPicker` and other public exports do not gain automatic isolation from the opt-in validation work. The internal `SignInDialog` is validated only -through an opt-in story. Any wider rollout requires a separate decision and -change. - -The detailed evidence, unresolved audits, and rollout gates live in the -[Shadow DOM isolation validation and rollout plan](../shadow-dom-isolation-plan.md). +through an opt-in story. Wider automatic isolation requires completing the +linked implementation groups and coordinated major-release gate. + +The detailed experimental evidence remains in the +[Shadow DOM isolation validation plan](../shadow-dom-isolation-plan.md). The +[production rollout policy](../shadow-dom-rollout-policy.md) records the public +component boundary, accepted limitations, implementation order, and release +gates without representing that planned behavior as shipped. diff --git a/docs/shadow-dom-consumer-compatibility.md b/docs/shadow-dom-consumer-compatibility.md index b5f9d3fc..3a5c943e 100644 --- a/docs/shadow-dom-consumer-compatibility.md +++ b/docs/shadow-dom-consumer-compatibility.md @@ -3,8 +3,9 @@ ## Purpose This contract records cross-browser evidence for consumer-facing behavior at -the SDK's Shadow DOM boundary. It is input to YPE-5356's production rollout -policy, not approval for automatic isolation beyond `YouVersionAuthButton`. +the SDK's Shadow DOM boundary. YPE-5356 incorporates it into the +[production rollout policy](shadow-dom-rollout-policy.md); this contract alone +does not enable automatic isolation beyond `YouVersionAuthButton`. The executable evidence lives in `consumer-compatibility.shadow-isolation.stories.tsx`. The existing @@ -37,7 +38,8 @@ exercise. They do not establish compatibility for every SDK component. | An ordinary document or Storybook-canvas selector finds SDK internals | Unsupported | DOM selector APIs do not cross a shadow boundary. `document.querySelector` and Testing Library queries rooted at the document need explicit open-root traversal. Automation behavior is tool-specific: [Playwright locators pierce open roots by default](https://playwright.dev/docs/locators#locate-in-shadow-dom), except for XPath locators, while closed roots remain inaccessible. | | A consumer traverses an open root and queries after attachment | Supported with timing and access constraints | Wait for the host's open `shadowRoot`, then query within it. The contract depends on the prototype's open-root policy and does not make internals a stable semantic API; prefer public refs, roles, and component callbacks where available. | | An automatically isolated component is nested inside another open SDK shadow root | Supported for basic rendering, traversal, and composed events | `NestedRootsRequireTraversalAndRetargetAtEveryBoundary` verifies recursive root traversal and target retargeting to the inner host in the outer scope and to the outer host in the document scope. Consumers must traverse every root explicitly. | -| Nested or concurrent overlays inside shadow roots | Unsupported by this contract | YPE-5355 owns stacking, focus, inertness, dismissal, and restoration. Basic nested-root evidence here does not change that overlay boundary. | +| Nested overlays inside shadow roots | Supported in current browser evidence | YPE-5355 verifies nested dialog and popover stacking, focus, inertness, dismissal, and restoration through the shared shadow-local portal infrastructure. Repeat component-specific validation during rollout. | +| Concurrent peer popovers inside the same or separate component roots | Unsupported as simultaneous peers | Opening a peer dismisses the current popover through Radix outside interaction. YPE-5356 accepts this single-active-peer behavior; supporting simultaneous peers requires a demonstrated product journey and separate design. | | Shadow-local ID relationships inside `BibleVersionPicker` | Supported in current browser evidence | `TopLayerEscapesClippingAndPreservesSemantics` verifies that the trigger and controlled panel remain in one root and Chromium, Firefox, and Playwright WebKit resolve their `aria-controls` relationship. This does not make cross-scope ID references supported. | ## Consumer risks @@ -62,28 +64,6 @@ assistive technologies remain unverified. Reflected ARIA element properties demonstrate DOM relationship resolution, not announcements or other assistive-technology behavior. -## Input for YPE-5356 - -The rollout policy should treat automatic isolation as a compatibility change -and require a component-specific audit before each rollout. In particular, it -must: - -- identify consumers that rely on native outer-form participation, external - labels or ARIA ID references, document-rooted queries, synchronous refs, or - unretargeted native events; -- prefer rollout candidates whose public callbacks, refs, and internal labels - already avoid those cross-scope dependencies; -- define consumer automation guidance around roles, public refs, and - tool-specific shadow behavior: [Playwright locators pierce open roots by - default](https://playwright.dev/docs/locators#locate-in-shadow-dom), while DOM - selector APIs need explicit traversal after root attachment and internal - rendering; -- preserve Firefox and WebKit coverage, define when to repeat actual-Safari - validation, and define required assistive-technology evidence rather than - treating browser DOM results as universal; and -- preserve YPE-5355's separate ownership of nested and concurrent overlay - behavior. - ## Follow-up work outside this ticket No production defect is fixed by this validation ticket. If a selected rollout @@ -91,6 +71,7 @@ component must participate in an outer native form or consume external labeling relationships, create a component-specific implementation ticket for an explicit public contract rather than relying on cross-scope browser behavior. The current ticket's actual-Safari smoke is recorded above. Recurring Safari and -assistive-technology validation, consumer-facing rollout documentation, and any -production implementation belong to YPE-5356 or separately authorized follow-up -tickets. No new Jira issue is created by this document. +deferred assistive-technology validation, consumer-facing release documentation, +and production implementation are assigned by the +[production rollout policy](shadow-dom-rollout-policy.md). No runtime behavior +or Jira issue is created by this compatibility document. diff --git a/docs/shadow-dom-isolation-plan.md b/docs/shadow-dom-isolation-plan.md index d435624e..a1452d46 100644 --- a/docs/shadow-dom-isolation-plan.md +++ b/docs/shadow-dom-isolation-plan.md @@ -1,5 +1,12 @@ # Shadow DOM Isolation Validation and Rollout Plan +YPE-5356 reconciles this research into the +[Shadow DOM production rollout policy](shadow-dom-rollout-policy.md). This file +remains the detailed evidence inventory; the policy owns the approved public +boundary, implementation groups, accepted limitations, and coordinated release +gate. Neither document changes the current integration-branch prototype by +itself. + ## Why this doc exists [ADR 0007](adr/0007-prototype-shadow-dom-style-isolation.md) records the durable @@ -25,8 +32,9 @@ This is a working plan, not approval for package-wide rollout. - Nested and concurrent overlays within and across component shadow roots were exercised through the real shared `ShadowRootHost` implementation (YPE-5355). The Shadow DOM ADR records the architectural boundary; the results below record - the supported contract and the remaining peer-dismissal gap. Broader component - rollout and peer-overlay coordination remain with YPE-5356. + the supported contract and the remaining peer-dismissal limitation. The + production policy accepts single-active-peer dismissal and assigns broader + component rollout to focused follow-up work. ## Nested and concurrent overlay evidence @@ -59,12 +67,12 @@ functions; assistive-technology checks remain open. | Portal lifecycle | Unit and browser coverage exercise lazy creation, exit-animation retention, cleanup, immediate reopen behavior, and the direct-Radix `VerseActionPopover` consumer. | Validated for shared primitives and the known bypass | Repeat the consumer audit when adding another direct overlay primitive. | | Dialog relationships | Browser coverage resolves title and description relationships inside the component tree. | Validated in Chromium, Firefox, Playwright WebKit, and an isolated local Safari 26.6.2 run | Verify announcements with real assistive technology. | | Dialog keyboard containment | Browser coverage exercises initial focus, programmatic escape redirection, forward and reverse traversal, radio-group collapsing, negative `tabindex`, and wraparound. | Validated in Chromium, Firefox, Playwright WebKit, and an isolated local Safari 26.6.2 run | Verify assistive-technology behavior. | -| Dialog modal lifetime | Coverage verifies inert background content while open and through staggered Content and Overlay exit animations. YPE-5355 also exercises both unmount orders for overlapping popover and dialog exits. | Validated for order-independent teardown | YPE-5356 owns peer concurrency across component roots. Verify assistive-technology behavior and repeat actual-Safari checks for significant platform changes. | +| Dialog modal lifetime | Coverage verifies inert background content while open and through staggered Content and Overlay exit animations. YPE-5355 also exercises both unmount orders for overlapping popover and dialog exits. | Validated for order-independent teardown | The rollout policy accepts single-active-peer dismissal. Verify assistive-technology behavior and repeat actual-Safari checks for significant platform changes. | | Dialog dismissal and restoration | Coverage exercises Escape, backdrop click, full-viewport hit testing, overlay-only focus, and restoration after both modal nodes unmount. | Validated in Chromium, Firefox, Playwright WebKit, and isolated local Safari 26.6.2 runs | Verify real screen-reader behavior. | | Consumer form participation | Browser coverage verifies that a light-DOM form does not own or serialize a native control inside an SDK shadow root. | Unsupported across tree scopes | Use an explicit component contract if a rollout target requires outer-form participation. | | Consumer labels and ARIA ID references | Browser coverage verifies that external native labels, `aria-labelledby`, and `aria-describedby` relationships do not resolve to controls inside the root. | Unsupported across tree scopes | Keep relationships in one tree scope; verify real assistive technology separately. | | Consumer events, refs, and automation | Coverage verifies native retargeting, the auth button's React handler and forwarded ref, open-root queries, and effect-driven attachment timing. | Supported with documented constraints | Repeat for each public component selected for rollout. | -| Nested shadow roots | Coverage verifies basic rendering, recursive queries, and event retargeting at each boundary. | Supported for the validated basics | Peer-overlay coordination remains with YPE-5356; verify assistive-technology behavior and repeat actual-Safari checks for significant platform changes. | +| Nested shadow roots | Coverage verifies basic rendering, recursive queries, and event retargeting at each boundary. | Supported for the validated basics | Single-active-peer dismissal is accepted; verify assistive-technology behavior and repeat actual-Safari checks for significant platform changes. | | Realistic same-page usage | YPE-5437 mounts, removes, and re-adds a 12-component mix in Normal and Strict Mode. Chromium, Firefox, Playwright WebKit, and local Safari 26.6.2 coverage verifies exact host counts, rendered scripture content, and one shared stylesheet object across roots and remounts. A production-build comparison found a small warm-run mount-cost difference on one machine. | No shared-host blocker found | Repeat user-visible performance and compatibility checks for each component selected for rollout. | Actual Safari 26.6.2 exposed a visual gap the initial focused assertions missed: @@ -92,20 +100,21 @@ input padding while the host button remains overridden. No other production direct-overlay bypass was found. The inventory therefore produced no equivalent low-risk migration and no materially different case that requires follow-up work. Extending the controller already owned by -`ShadowRootHost` is a candidate seam for added overlay coordination, not an ADR -decision. YPE-5356 owns whether and how to implement that coordination. +`ShadowRootHost` remains a possible seam if a product journey later requires +concurrent peer popovers. YPE-5356 does not require that speculative +coordination for the coordinated rollout. -## Blocking production-readiness decisions +## Production-readiness decisions -- Decide whether isolation is enabled per component instance, per public export, - or package-wide. +- Apply automatic isolation at the SDK-owned top-level component boundary. + Compound members and composed SDK children reuse the owning boundary rather + than creating accidental nested roots. - Apply [ADR 0007's client-only SSR and hydration contract](adr/0007-prototype-shadow-dom-style-isolation.md#ssr-and-hydration-contract) - per rollout component. YPE-5356 decides whether its first-paint, layout, and - no-JavaScript limitations are acceptable for that component. -- Resolve the YPE-5355 peer-dismissal gap before shipping concurrent peer - overlays (YPE-5356). The decision must consider trigger-time peer dismissal - and overlay order; ADR 0007 records the gap but does not select a coordination design. - Recurring actual-Safari and assistive-technology coverage still remain. + per rollout group. Each implementation ticket reviews whether its first-paint, + layout, and no-JavaScript limitations are acceptable for that component. +- Accept single-active-peer popover dismissal. Concurrent peer overlays require + a demonstrated product journey and a separate design. Recurring actual-Safari + and assistive-technology coverage still remain. - Keep the YPE-5400 custom-property contract and compiled-stylesheet prevention guard green as component styles change. The audit below closes the known ambient dependency; `all: initial` still does not reset custom properties. @@ -209,27 +218,17 @@ separately in YPE-5749. ## Research handoff and completion gate YPE-5356 is the convergence point for the Shadow DOM research. Its foundational -evidence comes from YPE-5298, YPE-5310, YPE-5352, and YPE-5353. It must not be -completed until the final findings from YPE-5354, YPE-5355, YPE-5400, YPE-5436, -and [YPE-5437](ype-5437-shadow-dom-realistic-usage.md) have been reconciled into -the rollout policy and these durable Shadow DOM documents. Any conflicts and -accepted limitations must be recorded rather than left implicit. +evidence comes from YPE-5298, YPE-5310, YPE-5352, and YPE-5353. The final +findings from YPE-5354, YPE-5355, YPE-5400, YPE-5436, +[YPE-5437](ype-5437-shadow-dom-realistic-usage.md), and YPE-5946 are reconciled +into the rollout policy and these durable Shadow DOM documents. Conflicts and +accepted limitations are recorded rather than left implicit. Every component rollout ticket produced by YPE-5356 must link back to that -policy and repeat the compatibility matrix for its selected component. Its gates -must cover browser and assistive-technology behavior, customization, -performance, and stylesheet failure recovery in addition to the component's -forms, labels, ARIA relationships, events, refs, queries, and overlays. - -## Rollout sequence - -1. Maintain YPE-5400's completed custom-property inventory and prevention guard. -2. Reconcile YPE-5354's SSR/hydration decision, YPE-5355's overlay findings, - YPE-5436's consumer contract, and YPE-5437's realistic-usage result in - YPE-5356. -3. Select the next public component and add component-specific compatibility, - browser, and accessibility coverage before enabling isolation. -4. Publish consumer guidance for DOM queries, automation, customization, forms, - accessibility, and the loss of global CSS styling. -5. Repeat the validation matrix for each component rather than assuming that the - infrastructure proof covers its component-specific behavior. +policy and apply the compatibility matrix to its selected component. Its gates +cover browser and assistive-technology claims, customization, performance, and +stylesheet failure recovery in addition to the component's forms, labels, ARIA +relationships, events, refs, queries, and overlays. + +The dependency-ordered rollout sequence and release gate are defined in the +[production rollout policy](shadow-dom-rollout-policy.md#implementation-plan). diff --git a/docs/shadow-dom-rollout-policy.md b/docs/shadow-dom-rollout-policy.md new file mode 100644 index 00000000..e24b2819 --- /dev/null +++ b/docs/shadow-dom-rollout-policy.md @@ -0,0 +1,215 @@ +# Shadow DOM Production Rollout Policy + +## Status and intent + +YPE-5356 approves a coordinated package-wide rollout plan for compatible public +UI components. It does not claim that the rollout has shipped. Until every +included implementation group and the release gate below are complete, the +runtime behavior remains the prototype recorded in +[ADR 0007](adr/0007-prototype-shadow-dom-style-isolation.md): only +`YouVersionAuthButton` creates an automatic shadow boundary. + +The rollout is coordinated at release time, not implemented in one change. +Focused component or component-group tickets may land independently on the +Shadow DOM integration branch, but the package must not release a partial public +boundary. Runtime feature flags and a phased-release framework are unnecessary. + +## Decisions + +- Automatic isolation is applied at an SDK-owned top-level component boundary, + not to every exported React function. Compound members and implementation + children stay in their owning root's tree. +- Public components that compose other included SDK components must reuse their + outer SDK boundary. The implementation must not create accidental nested + roots merely because both public exports support automatic isolation. +- The client-only SSR contract is accepted for the included boundary, subject + to a focused first-paint review in each implementation ticket. A component + that needs server-rendered or no-JavaScript content must be excluded or use a + separately approved host strategy. +- Automatic isolation is a breaking rendered-DOM change and ships in a major + release. React props need not change, but document queries, native event + targets, ref timing, global styling, and cross-tree relationships can change. +- Real assistive-technology validation is deferred. Automated keyboard and DOM + semantics remain required, and release notes must not imply verified screen- + reader behavior. + +## Public component boundary + +The inventory follows the package's public entrypoint, +`packages/ui/src/index.ts`, with `packages/ui/src/components/index.ts` as its +primary component barrel. Types, constants, and helper functions are not +component rollout targets. + +| Public export | Disposition | Automatic boundary | Required implementation or reason | +| --- | --- | --- | --- | +| `YouVersionAuthButton` | Included; already prototyped | The button | Preserve its current ref and event contract and include it in the final release checks. | +| `BibleChapterPicker.Root`, `.Trigger`, `.Content` | Included as one compound component | `Root` only | Keep context, trigger, content, and shadow-local popover in one tree. Audit consumer-supplied trigger children, callbacks, focus, and picker geometry. | +| `BibleVersionPicker.Root`, `.Trigger`, `.Content` | Included as one compound component | `Root` only | Promote the validated opt-in host to the public root and audit custom trigger styling, storage, focus, and native top-layer behavior. | +| `BibleLanguagePickerContent`, `BibleVersionPickerLanguageTrigger` | Included transitively | No independent boundary | Both require `BibleVersionPicker.Root` context and stay inside that root. Direct use outside the root is already unsupported. | +| `BibleReader.Root`, `.Content`, `.Toolbar` | Included as one compound component | `Root` only | Keep reader content, toolbar, pickers, settings, verse actions, and dialogs in one boundary. Audit consumer children, scrolling, selection, overlays, focus, refs, and first paint. | +| `BibleThemeSettingsContent` | Included | Its standalone mount, or the owning reader boundary | Preserve its Expo DOM callback contract and avoid a nested boundary when rendered by `BibleReader`. | +| `BibleTextView` | Included | Its standalone mount, or the owning card/reader boundary | Preserve scripture rendering, footnote portals, selection callbacks, and reader stylesheet behavior without nesting roots inside composed SDK components. | +| `FootnoteContent` | Included | Its standalone mount, or the owning scripture boundary | Treat it as a leaf when used alone and reuse the enclosing `BibleTextView` boundary otherwise. | +| `VerseOfTheDay` | Included | The card | Audit loading/error states, Web Share and clipboard callbacks, scripture direction, and first-paint geometry. Its internal `BibleTextView` reuses the card boundary. | +| `BibleCard` | Included | The card | Audit loading/error states, optional version picker, footnotes, highlights, sizing, and first paint. Internal picker and scripture components reuse the card boundary. | +| `ProfileAvatar` | Included | The avatar | Audit image loading, fallback labeling, consumer props, ref behavior inherited from Radix, and compact inline layout. | +| `Separator` | Included | The separator | Audit orientation, decorative semantics, consumer props, and flex/grid sizing through a `display: contents` host. | +| `Textarea` | Excluded | None | Native outer-form ownership, serialization, and external `