Skip to content

fix(ui): enforce Shadow DOM custom property contract (YPE-5400) - #394

Open
abharms wants to merge 4 commits into
journey-to-the-shadow-domfrom
ype-5400-css-custom-property-audit
Open

fix(ui): enforce Shadow DOM custom property contract (YPE-5400)#394
abharms wants to merge 4 commits into
journey-to-the-shadow-domfrom
ype-5400-css-custom-property-audit

Conversation

@abharms

@abharms abharms commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • audit authored and compiled UI custom properties across SDK, embedded core, Tailwind, tw-animate-css, and Radix inputs
  • remove the test-only Tailwind class that accidentally emitted an ambient --radius dependency
  • parse the real compiled stylesheet during verify:styles and fail builds on unexplained reference-only custom properties
  • document the light-DOM consumer contract, exact runtime-input exceptions, ownership, and the guard's intentional name-level limitation
  • add a UI patch changeset

Requirement evidence

Jira acceptance area Evidence
Source and compiled inventory The rollout plan records the authored audit and compiled totals: 201 declared/initialized names, 151 referenced names, and zero unexplained references after the fix.
Property classification The contract classifies SDK-owned --yv-*, supported light-DOM inputs, generated Tailwind state, compile-time theme aliases, Radix runtime inputs, tw-animate-css fallbacks, and forbidden ambient names.
Existing customization behavior The README remains unchanged: document-level --yv-* overrides are supported for light-DOM [data-yv-sdk] components, while the automatically isolated YouVersionAuthButton gains no public shadow-root token API.
Local fixes versus follow-up The accidental compiled --radius dependency is removed. The separate inert VerseActionPopover animation-property behavior is linked and documented under YPE-5749.
Compiled prevention guard verify-styles.js parses dist/tailwind.css with css-tree, accepts only locally supplied names or nine exact reviewed exceptions, and fails on new unexplained references. Focused tests cover nested references, registrations, parser lookalikes, exact exceptions, and rejection.
Durable documentation The rollout plan owns the complete contract and exception rationale; ADR 0007 points to that completed evidence.

Review scope

Required outcomes:

  • inventory and classify custom-property declarations and references in authored UI inputs and the actual compiled stylesheet, including embedded core and third-party CSS
  • preserve the documented light-DOM customization contract without introducing a public shadow-root token API
  • remove local forbidden ambient dependencies and enforce the compiled contract with a documented exact allowlist
  • document the contract, exceptions, and guard limitation in the Shadow DOM rollout record

Permitted support work:

  • a focused css-tree validator and adjacent declaration file
  • wiring through the existing compiled-style verifier
  • consolidated unit tests, removal of the test-only negative Tailwind scan input, a minimal ADR pointer update, dependency metadata, and a UI patch changeset

Non-goals:

  • selector- or cascade-reachability analysis; the guard is intentionally name-level
  • a public shadow-root customization API, broader Shadow DOM rollout, third-party CSS rewrites, namespace wildcard exceptions, or a permanent TypeScript source scanner
  • changing the VerseActionPopover animation custom properties; that behavior is tracked in YPE-5749
  • changing the existing README customization promise

Please treat a finding as blocking only when it identifies an unmet in-scope requirement, a documented repository-standard violation in added or modified code, or a concrete regression or defect caused or worsened by this diff. Label other valid improvements as non-blocking follow-ups.

Verification

  • pnpm --filter @youversion/platform-react-ui exec vitest run --project unit src/styles/custom-property-contract.test.ts src/components/ui/input-group.test.tsx — 3/3 passed
  • pnpm --filter @youversion/platform-react-ui build — passed; compiled style verification reports zero unexplained references
  • pnpm lint — passed
  • pnpm typecheck — passed
  • pnpm turbo test --concurrency=1 -- --maxWorkers=1 — passed: core 432/432, hooks 318/318, UI 543/543
  • pnpm turbo build --force — passed, 4/4 build tasks uncached
  • final independent Standards, Spec, and Compatibility audits at 9bcb473 — no required findings

The default concurrent test invocation twice exposed existing five-second timeouts in bible-reader-controlled.test.tsx; the affected file passed 33/33 in isolation and the complete sequential repository suite passed 1,293/1,293.

Compatibility and limitations

  • Published runtime code, public exports, component DOM, and the README contract are unchanged.
  • The generated stylesheet loses exactly one orphan selector created by the removed negative test literal; the production --yv-radius InputGroup selector remains.
  • The exact exception ledger is intentionally duplicated in executable code and durable documentation so both builds and reviewers can enforce ownership without adding generation machinery.
  • The guard proves artifact-wide name ownership, not that each declaration reaches each reference through the cascade.

Jira

Greptile Summary

The PR establishes a compiled-stylesheet custom-property ownership guard for the UI package and documents the audited Shadow DOM contract.

  • Adds a css-tree-based audit of declarations, registrations, references, and exact runtime exceptions.
  • Runs the audit against the final compiled UI stylesheet during style verification.
  • Adds focused tests, removes the test-only ambient --radius input, documents ownership and limitations, and records a UI patch release.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/ui/scripts/custom-property-contract.js Adds the structural custom-property inventory and exact exception ledger; no blocking defect was established.
packages/ui/scripts/verify-styles.js Applies the new audit to the final compiled stylesheet and reports unexplained references through the existing build gate.
packages/ui/src/styles/custom-property-contract.test.ts Covers declarations, initialized registrations, nested references, parser lookalikes, exact exceptions, and near-match rejection.
packages/ui/package.json Adds a Node-compatible build-time parser dependency that is available in the repository's clean CI and release installations.
docs/shadow-dom-isolation-plan.md Documents custom-property ownership, exact exceptions, consumer boundaries, and the validator's intentional name-level limitation.
pnpm-lock.yaml Locks css-tree 3.2.1 and its transitive dependencies for frozen CI and release installations.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Inputs[UI, core, Tailwind, and third-party CSS] --> Compile[Compile dist/tailwind.css]
  Compile --> Mutate[Preserve host revert-layer rule]
  Mutate --> Bundle[Embed compiled CSS in UI JavaScript]
  Mutate --> Audit[Parse custom-property declarations and references]
  Allowlist[Exact reviewed runtime exceptions] --> Audit
  Audit -->|No unexplained references| Verify[Style verification passes]
  Audit -->|Unexplained reference| Fail[Build fails]
Loading

Reviews (4): Last reviewed commit: "test(ui): strengthen custom property con..." | Re-trigger Greptile

Context used:

@changeset-bot

changeset-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f51fe2c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@youversion/platform-react-ui Patch
vite-react Patch
@youversion/platform-core Patch
@youversion/platform-react-hooks Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T19:54:47.804190Z f51fe2c New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@abharms
abharms force-pushed the ype-5400-css-custom-property-audit branch from 2b5f030 to 9bcb473 Compare September 10, 2026 17:07
@abharms
abharms marked this pull request as draft September 10, 2026 17:13
@abharms
abharms marked this pull request as ready for review September 11, 2026 19:11

@cameronapak cameronapak left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review

YPE-5400

Summary

Standards: 0 must-fix. Spec: 0 must-fix. Primary concern: none.

For Agents
  • CI: passing
  • Bot review: clear
  • Event: APPROVE
  • HEAD: f51fe2c

Written by Code Reviewer bot on behalf of Cam.

inherits: false;
initial-value: 0s;
}
@property --uninitialized-input {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

praise: Treating registrations without an initial value as inputs keeps this audit honest instead of mistaking syntax for a supplied runtime value.

For Agents: registration coverage

This focused test distinguishes an initialized @property --registered-duration from @property --uninitialized-input, while also covering nested fallbacks and comment and string lookalikes.

Written by Code Reviewer bot on behalf of Cam.

@@ -0,0 +1,111 @@
import { parse, walk } from 'css-tree';

const referenceOnlyExceptions = new Map([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: Could this allowlist be a grouped set? Its metadata is never read, and the rollout document already owns the same rationale.

For Agents: simplify the exception ledger

referenceOnlyExceptions is only queried through .has(name). Replacing the map entries with a Set of the nine exact names and short group comments would remove roughly 35–40 lines and one duplicated source of explanatory text without weakening enforcement. The parser and compiled-artifact check should remain unchanged.

Written by Code Reviewer bot on behalf of Cam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants