Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6cd3a16
test(ui): add cross-browser shadow DOM coverage
abharms Sep 21, 2026
846872d
test(ui): stabilize Firefox story readiness
abharms Sep 22, 2026
b4a04a7
Merge remote-tracking branch 'origin/journey-to-the-shadow-dom' into …
abharms Sep 22, 2026
cae4444
fix(ci): stabilize shadow browser jobs
abharms Sep 22, 2026
201d134
test(ui): make overlay exit ordering deterministic
abharms Sep 22, 2026
648102b
test(ui): await Storybook assertions
abharms Sep 22, 2026
a632b7d
fix(ci): avoid Firefox matcher teardown rejections
abharms Sep 22, 2026
744bf47
test(ui): await shadow browser assertions
abharms Sep 22, 2026
2dce286
test(ui): stabilize shadow browser timing
abharms Sep 22, 2026
4cb81b2
test(ui): consume shadow story wait promises
abharms Sep 22, 2026
5f574ed
test(ui): avoid matcher promises in shadow stories
abharms Sep 22, 2026
4966bcd
fix(ui): handle language sync rejections
abharms Sep 22, 2026
2bf711b
test(ui): log browser unhandled rejection details
abharms Sep 22, 2026
2e5e7c1
test(ui): ignore expected font load events in firefox
abharms Sep 22, 2026
f519357
test(ui): filter firefox stylesheet load events
abharms Sep 22, 2026
c097d66
test(ui): ignore browser events in lifecycle assertion
abharms Sep 22, 2026
b7339f4
test(ui): narrow font rejection filter
abharms Sep 22, 2026
e103528
test(ui): harden shadow DOM cross-browser evidence
abharms Sep 23, 2026
6916b78
fix(ui): preserve shadow picker spacing in Safari
abharms Sep 23, 2026
6b32dc0
fix(ui): preserve document-root spacing in Safari
abharms Sep 23, 2026
c799eb6
test(ui): cover spacing fixes cross-browser
abharms Sep 24, 2026
03d7661
test(ui): reuse Storybook wait helper
abharms Sep 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-browsers-test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@youversion/platform-react-ui': patch
---

Preserve SDK spacing in Safari inside Shadow DOM and document roots by scoping the host CSS reset and inlining the fixed Tailwind spacing scale.
34 changes: 34 additions & 0 deletions .github/workflows/storybook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,37 @@ jobs:
YVP_APP_KEY: ${{ secrets.YVP_APP_KEY }}
STORYBOOK_YOUVERSION_API_HOST: ${{ secrets.YVP_API_HOST }}
STORYBOOK_YOUVERSION_APP_KEY: ${{ secrets.STORYBOOK_YOUVERSION_APP_KEY }}

shadow-browser-tests:
name: Shadow DOM (${{ matrix.browser }})
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.56.1-noble
strategy:
fail-fast: false
matrix:
browser: [firefox, webkit]
env:
VITEST_BROWSER: ${{ matrix.browser }}
STORYBOOK_YOUVERSION_APP_KEY: ${{ secrets.STORYBOOK_YOUVERSION_APP_KEY }}
STORYBOOK_AUTH_REDIRECT_URL: ${{ secrets.STORYBOOK_AUTH_REDIRECT_URL }}
steps:
- uses: actions/checkout@v5

- name: Setup pnpm
uses: pnpm/action-setup@v4

- uses: actions/setup-node@v6
with:
node-version: 24

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build packages
run: pnpm build

- name: Run Shadow DOM browser tests
run: cd packages/ui && pnpm run test:shadow-browser
env:
HOME: /root
34 changes: 20 additions & 14 deletions docs/shadow-dom-consumer-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## Purpose

This contract records the Chromium 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`.
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 executable evidence lives in
`consumer-compatibility.shadow-isolation.stories.tsx`. The existing
Expand All @@ -30,15 +30,15 @@ exercise. They do not establish compatibility for every SDK component.
| --- | --- | --- |
| A light-DOM form natively owns or serializes a control inside an SDK shadow root | Unsupported | `FormsAndExternalRelationshipsStopAtTheTreeScope` verifies that the isolated textarea has no owner form, is absent from `form.elements`, and is absent from `FormData`. A rollout target that needs form participation requires an explicit component API or separately designed form-associated host contract. |
| A light-DOM `<label for>` labels or focuses a control inside an SDK shadow root | Unsupported | The same story verifies that `label.control` is `null` and clicking the label does not focus the isolated textarea. Put the label and control in the same tree scope or expose an explicit component labeling API. |
| An internal control resolves light-DOM `aria-labelledby` or `aria-describedby` ID references | Unsupported | The attributes remain present, but Chromium's reflected element arrays are empty across the boundary. Keep referenced nodes in the same tree scope. This DOM evidence is not a substitute for assistive-technology testing. |
| An internal control resolves light-DOM `aria-labelledby` or `aria-describedby` ID references | Unsupported | The attributes remain present, but reflected element arrays are empty across the boundary in Chromium, Firefox, and Playwright WebKit. Keep referenced nodes in the same tree scope. This DOM evidence is not a substitute for assistive-technology testing. |
| A native composed event crosses one shadow boundary | Supported with native retargeting | `EventsRefsAndDomQueriesExposeDifferentConsumerViews` clicks an internal label element and verifies that a light-DOM listener receives the shadow host as `event.target`; `composedPath()` begins with the label and includes the internal button and host. Consumers must not assume an external native listener's target is the internal control. |
| A React handler passed to `YouVersionAuthButton` receives its button event | Supported for this public component | The same story verifies that the consumer `onClick` handler receives the internal originating label as `target` and the internal button as `currentTarget`. Consumers may rely on the button current target, not on every event originating at the button itself. This is component-specific evidence, not a package-wide promise for every event prop. |
| A forwarded `YouVersionAuthButton` ref exposes the internal button | Supported after mount | The ref resolves to the exact internal `HTMLButtonElement`. It remains `null` through the consumer's first layout effect because the shadow root attaches in a passive effect; consumers must handle callback-ref updates or read object refs after a later commit. |
| 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. |
| Shadow-local ID relationships inside `BibleVersionPicker` | Supported in current Chromium evidence | `TopLayerEscapesClippingAndPreservesSemantics` verifies that the trigger and controlled panel remain in one root and Chromium resolves their `aria-controls` relationship. This does not make cross-scope ID references supported. |
| 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

Expand All @@ -52,9 +52,14 @@ An open root permits inspection and mutation by same-page JavaScript, so it is
an automation and styling boundary rather than a security boundary. Selectors
that depend on internal markup remain fragile even when they traverse the root.

The executable suite currently runs only in Chromium. Firefox, WebKit, and real
assistive technologies remain unverified. Chromium's reflected ARIA element
properties demonstrate DOM relationship resolution, not announcements or other
The focused Shadow DOM suite runs in Chromium, Firefox, and Playwright WebKit.
All 22 current stories returned assertion-level success in local Safari 26.6.2
through SafariDriver when each ran in a fresh browser session. A single
long-lived SafariDriver session stalled on the sign-in dialog and verse action
popover stories after 20 successes, so isolated sessions are required for this
smoke setup. Actual Safari is not part of continuous integration. Real
assistive technologies remain unverified. Reflected ARIA element properties
demonstrate DOM relationship resolution, not announcements or other
assistive-technology behavior.

## Input for YPE-5356
Expand All @@ -73,8 +78,9 @@ must:
default](https://playwright.dev/docs/locators#locate-in-shadow-dom), while DOM
selector APIs need explicit traversal after root attachment and internal
rendering;
- define required Firefox, WebKit, and assistive-technology evidence rather than
treating the Chromium results as universal; and
- 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.

Expand All @@ -84,7 +90,7 @@ No production defect is fixed by this validation ticket. If a selected rollout
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.
Cross-browser 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.
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.
Loading
Loading