WIP: feat!: YPE-4113 - #320
Open
cameronapak wants to merge 20 commits into
Open
Conversation
Measures how much consuming-app global CSS reaches SDK components. Five hostile CSS groups (preflight, bareElements, aggressiveReset, inheritedTypography, important) inject after the SDK style tag, matching real consumer source order. Snapshots poll until two consecutive reads agree. A single-frame read measured CSS transitions rather than leaks, reporting ~101 leaks for every group including `important`, which sets only two properties. Preflight embeds the CLI-compiled output, not the source. The source uses `--theme(...)` build-time calls that a browser drops as invalid. Assertions document the current leak. Phases 3 and 4 narrow them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 4 gates all SDK CSS on [data-yv-sdk], so an unstamped root loses its styling. Five roots carried no attribute: ProfileAvatar, Separator, Textarea, the Dialog overlay, and BibleVersionPickerLanguageTrigger. The stamp is not inert. theme.css declares light tokens on the bare [data-yv-sdk] selector with dark as a nested override, so an element that gains the attribute inside a dark scope reverts to light. Every stamp names the enclosing scope's theme, not the provider's. FootnoteContent wrote data-yv-theme from an optional prop with no default, so React dropped the attribute and it rendered light inside a dark reader. It now falls back to useTheme(). ui/button.tsx is deliberately not stamped. It has no theme in scope, it is not a public export, and Phase 4's descendant arm covers it. scope-attribute.test.tsx asserts every component export carries the attribute, so a new component cannot forget. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A consumer's `body { letter-spacing: .5em }` never matches an SDK
element, so no specificity or cascade rank stops it. The value arrives
by inheritance, which only applies where the element declares nothing.
Declaring each property closes that channel.
Pins color, font-family, font-variant, letter-spacing, line-height,
text-align, text-indent, text-shadow, text-transform, white-space, and
word-spacing. `direction` stays out: bible-reader.css handles RTL and
`text-align: start` is direction-aware.
font-family was not in the plan. The reset applied it only to
`[data-yv-sdk] *`, so the marked element itself always inherited the
host font. The reader font still wins at 0,1,0 over the reset's 0,0,0.
Story assertions moved from a combined total to per-group. The groups
cancel: preflight sets box-sizing: border-box and aggressiveReset sets
content-box, so the combined value hid a real leak.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…layers SDK CSS sat in @layer yv-sdk-*, which loses to a consumer's unlayered CSS at any specificity. That is what layers are for, so raising specificity inside the layer could never help. Protection now comes from territory, not cascade rank. scripts/scope-selectors.mjs rewrites the compiled sheet after Tailwind runs, prepending :is([data-yv-sdk], [data-yv-sdk] *) to every selector that is not already gated. Built on Lightning CSS: its Selector visitor is a typed structural API, so escaped class names like .yv\:mt-4 cannot be mangled, and @keyframes/@font-face/@Property produce no Selector nodes and are skipped by construction. It re-parses its own output and fails the build on any ungated selector. :root and :host stay unscoped; they define only --yv-* variables and render nothing. build:css now writes .cache/tailwind.raw.css, gitignored, so the ungated sheet can never reach dist/ and be published. Two extra declarations in theme.css that gating alone cannot replace, because both are specificity-independent: - color: inherit on descendants. A consumer's `ul { color: … }` matches SDK elements directly, so pinning color on the root does nothing. - box model on the root itself. The universal block is [data-yv-sdk] *, which misses the marked element; `* { box-sizing }` does not. tsconfig.build.json excludes src/test. Phase 1's harness iterates NodeList under a lib without DOM.Iterable, breaking the declaration build. dist/tailwind.css grows 15.7% raw, 3.2% gzipped. Selector, @Property and @Keyframes counts are unchanged, so no rules were lost. BREAKING CHANGE: consumer CSS can no longer override SDK declarations. --yv-* token overrides on [data-yv-sdk] still work and remain the supported customization surface. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ADR-0005 covers four decisions in one place, because the layer question has already flipped three times and the last reversal was documented only in a changelog entry. It records why layers can never win, why @scope was rejected, why shadow DOM is not adopted, and why there is no consumer opt-out prop. The residual report is generated from harness output, not written by hand. Two consumer declarations still reach SDK components, both using !important: `button { padding: 2rem !important }` reaches 155 buttons, `button { border-radius: 0 !important }` reaches 65 of them. All four non-!important groups report zero on every component. ADR-0005 set the shadow DOM threshold before the numbers existed: recommend it if and only if the residual includes a non-!important rule. There is none, so it is not adopted. AGENTS.md drops the strip-layers.js reference, which named a script deleted in 694325f, and documents the real build chain. README.md states that consumer CSS targeting SDK internals is unsupported and now blocked, while --yv-* token overrides still work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment and prose only. No selector, declaration or code path changes. The full suite still passes, including the 14 hostile-host browser tests at zero leaks. theme.css also moves the inherited-properties comment down to the block it describes. It sat above the box-model block, which made it read as documentation for the wrong four declarations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 0117634 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
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 |
cameronapak
commented
Aug 7, 2026
Comment on lines
+69
to
+72
| - **You cannot style SDK internals, and we never supported it.** If you target | ||
| our elements from your stylesheet, our rule overrides yours. Class names, | ||
| `data-slot` values and DOM structure are internal. They change without a major | ||
| version. |
Document the Dialog portal stamp exception and add hostile-host stories for the public language-picker exports. Co-authored-by: Cursor <cursoragent@cursor.com>
Use partner/product language for the style-isolation fixture and docs. Co-authored-by: Cursor <cursoragent@cursor.com>
Finish the hostile→consumer rename across stories, tests, Storybook, and docs. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Splits the compiled sheet in two, by property. Every property on a new exemption list stays unlayered and normal, exactly where it was. Every other declaration goes into a declared `@layer yv` and gets `!important`. CSS Cascade 5 §6.1 sorts by importance, then by layer, then by specificity, and §6.4.4 reverses layer order for important declarations while ranking unlayered CSS last. A layered important SDK declaration therefore beats an unlayered important consumer rule at any specificity, and beats an id selector too, because importance is compared first. The layer cannot hold the whole sheet. A layered normal declaration loses to ordinary unlayered consumer CSS, which is the failure mode ADR-0005 was written to escape. Wrapping everything measured 14 harness failures, all in font-size, line-height and border-*-width, all exempt properties demoted below plain Tailwind Preflight. The exemption list is by property family, with a reason each: keyframe animated properties, the transform pipeline, animation and transition controls, the properties Radix and floating-ui write inline, the ones SDK components write inline, and every custom property. An author !important outranks both the inline style attribute and the CSS animation origin, so importantizing any of them removes runtime behaviour rather than winning an argument. The pass is text surgery on printed CSS, not a Lightning CSS visitor. Lightning CSS 1.31.1 cannot round-trip a declaration whose value holds var() through the JS visitor API; it throws "failed to deserialize; expected an object-like struct named Specifier". Safety comes from re-parsing the finished text. The verification now also fails on a non-exempt normal declaration, an exempt important declaration, an !important inside @Keyframes, and a missing or misplaced layer block. verify-styles.js asserts @layer yv{ reaches dist/index.js. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a sixth consumer-host group, highSpecificity, at 1,0,1:
#yv-consumer-host-root button { padding; margin; border-radius }
The id goes on document.body, not on #storybook-root. Radix portals the
popovers and dialogs straight into document.body, and a fixture that
cannot reach the portalled DOM tests half of the components. It is set
on every injection, including the clean baseline, so the DOM the diff
compares is identical across snapshots.
The important group changes from a recorded residual to an asserted
zero, and highSpecificity asserts zero as well. Both fixtures target
button, so a component that renders none measures zero for an
uninteresting reason. Every story that does render one now also asserts
that the button matches the fixture selector and that the style tag is
still in the document, so a zero cannot come from a fixture that
stopped matching.
Measured before this change, on the ADR-0005 sheet: important 944
leaks, highSpecificity 1570, over fifteen components. After: 0 and 0.
ConsumerTokenOverrideStillApplies and every existing zero-leak
assertion still pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dual Adds ADR-0006 with the cascade math, the split design and why the whole sheet cannot go in the layer, the full property exemption list with a reason per family, the Lightning CSS var() visitor limitation, and the two residuals that remain. ADR-0005 keeps its gate decision and gains a cross-link plus superseded markers on the two statements that changed. Rewrites the residual-leak report against the new measurement. All six consumer-CSS groups now report zero on all fifteen components. The two residuals are a consumer rule that is important and in a layer declared before yv, and the exempt properties, which keep their pre-change cascade position. Both are deliberate and neither is in the fixture. The report also names what no automated check covers: popover placement and enter/exit animation appearance. The README tells consumers that !important and id selectors no longer override us, and which properties are still theirs. The changeset says the same to anyone reading the release notes. packages/ui/AGENTS.md replaces the "SDK CSS is in no layer, on purpose" bullet with the split, and records the rule that keeps the exemption list honest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a seventh consumer-CSS group, `remRebase`, that injects nothing but
`html { font-size: 62.5% }`, and assert zero leaks on every component.
A `rem` resolves against the document root element. A host page that
rescales the root therefore rescales every `rem` the SDK ships, and no
selector, cascade layer or `!important` reaches that. The leak is in the
unit, not in the cascade, so the six existing groups could never see it.
The group leaks 2,098 computed-style differences against the current
sheet. The assertions here are red until the build converts the sheet's
`rem` lengths to `px`.
Each story also asserts that the document root really computes to 10px.
The harness injects into `document.head`, so without that positive
control a zero could mean the fixture stopped applying.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A host `html { font-size: 62.5% }` shrank every SDK size by 37.5 percent.
The `remRebase` harness group measured 2,098 leaks. It now measures 0.
Three changes close it:
1. `scope-selectors.mjs` gains a Lightning CSS `Length` visitor that
rewrites every `rem` length to `px` at 1rem = 16px. That is the
browser default, so nothing changes on an unmodified host page. The
visitor reaches declaration values, custom properties, `calc()`,
`var()` fallbacks, media query conditions and `@keyframes` frames. A
text replace was not an option: the string `rem` also appears in
escaped class names Tailwind generates from arbitrary values, and in
the `.rem` class from bible-reader.css.
2. `global.css` declares `font-size: 16px` on `[data-yv-sdk]`. That
closes the second channel, inheritance: theme.css declares every
other inherited text property on the root but not this one, so the
root took the consumer's body value. `font-size` is exempt, so the
declaration stays unlayered and normal and a consumer can still
override it. That override is the accessibility escape hatch.
3. Three inline `rem` literals in component source become `px`. An
inline style never passes through the build, so the rebase cannot
reach it.
`verifyOutput` re-parses the result and fails the build on any `rem`
that survives.
Media query `rem` is converted for a different reason than the rest.
Media Queries Level 4 resolves a relative unit in a query against the
initial value of `font-size`, not the root element's computed value, so
a host `html { font-size }` never moved our breakpoints.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR-0007 holds the decision, the measurement and the trade-off.
It also records the alternative the handoff proposed, with data. Setting
an explicit `font-size` on the `[data-yv-sdk]` root removed 10 of 120
leaks on the experiment story, or 8.3 percent, because a `rem` resolves
against the document root and never against an ancestor. It is not a
rival to the rem rebase; it is the smaller half of the same fix, and it
closes the inheritance channel. Both shipped.
The cost is stated plainly: the SDK no longer grows when a reader raises
their browser's default font size. Browser zoom still works, because
zoom scales `px`. A consumer who needs type scaling can override
`[data-yv-sdk] { font-size }`, raise a `--yv-*` token, or pass a
component's `fontSize` prop.
The residual-leak report gains the seventh group, the per-component
before and after, and a third residual. The changeset gains one
paragraph on the consumer-visible change. README and AGENTS.md gain the
same in short form.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gate's descendant arm matched every element inside a stamped subtree, including content a consumer passed in as children or as render-prop output. theme.css recolored that content and reset its box model, and after the layered-important change those declarations carried !important too. Nobody had measured that direction. The gate's descendant arm is now `[data-yv-sdk] *:not([data-yv-slot], [data-yv-slot] *)`, and the descendant blocks in theme.css carry the same exclusion. A new ConsumerSlot wrapper stamps data-yv-slot on the two trigger sites that can take a wrapper without changing the rendered DOM shape. The harness gains a reverse direction. It diffs a consumer child's computed styles inside a slot against the same markup outside every SDK subtree, in one document, with the SDK sheet present for both. The three slotted placements read 0, down from 255, 68 and 68. The gate now adds 0,2,0 instead of 0,1,0. The dark custom variant gains the same exclusion, so dark utilities stay above base utilities. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR-0008 has the decision, the before and after numbers, the baseline it measured against, and the three sites that cannot take a wrapper. ADR-0005, 0006 and 0007 gain a Status amendment pointing at it. The residual-leak report gains the reverse direction and two new residuals. One is consumer content that is not in a slot: the gate's rise from 0,1,0 to 0,2,0 lifted the exempt half's normal declarations above an ordinary consumer rule, so that content went from 255 leaks to 416. The other is a selector whose subject sits past the gate compound. AGENTS.md gains the slot-stamping rule. The README and the changeset tell consumers their own content inside our components keeps their styling, and name the two places they must stamp themselves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The slot exclusion shipped as a bare :not(). That takes the specificity of its most specific argument, so it raised the gate from 0,1,0 to 0,2,0 across the whole sheet. Uniform is not harmless here. The sheet ships in two halves, and the unlayered half holds normal declarations whose only defence is specificity. The rise lifted font-size, background-color and the border-*-width longhands above an ordinary consumer rule at 0,1,0. Measured on the harness: consumer content that is not in a slot went from 255 leaks to 416, which is worse than before the slot work. The exclusion now sits inside :where(), which contributes nothing. The gate is back at 0,1,0, the no-slot control is back at 255, and the three slotted placements stay at 0. The dark custom variant needed no compensation once the gate stopped moving: a base utility is 0,2,0 and a dark: utility is 0,3,0, the same one-step margin as before. The build check accepts a hand-written bare :not() too. It excludes the same elements; it just pays for the privilege. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR-0008 now says the gate stays at 0,1,0, why the placement of :where() is the whole decision, and what the bare :not() cost when it shipped first. The alternatives section keeps the rejected form with its number rather than dropping it. The residual report's control row goes back to 255 and says why that number has to hold: content the SDK still reaches must not be reached harder than before. Residual 4 loses its regression and keeps the history. ADR-0005 and ADR-0006 amendments now say their specificity claims still stand. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
||
| function main() { | ||
| const options = parseArguments(process.argv.slice(2)); | ||
| const ok = runOnce(options); |
Contributor
There was a problem hiding this comment.
Initial stylesheet write is missed
When Tailwind creates .cache/tailwind.raw.css after the initial runOnce check but before fs.watch is registered, the write produces no observed event and has no retry, leaving dist/tailwind.css missing or stale until another Tailwind rebuild occurs.
Knowledge Base Used: packages/ui
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/scripts/scope-selectors.mjs
Line: 1442
Comment:
**Initial stylesheet write is missed**
When Tailwind creates `.cache/tailwind.raw.css` after the initial `runOnce` check but before `fs.watch` is registered, the write produces no observed event and has no retry, leaving `dist/tailwind.css` missing or stale until another Tailwind rebuild occurs.
**Knowledge Base Used:** [packages/ui](https://app.greptile.com/youversion/-/custom-context/knowledge-base/youversion/platform-sdk-react/-/docs/ui-package.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is for demo purposes at this time.
YPE-4113 | Artifacts | Task | PR Walkthrough (alpha)
What problems was I solving
A partner puts
BibleTextViewinto their app. Theirbutton { padding: 1rem }reshapes our version picker trigger. Their
body { font-family: Comic Sans }rewrites our verse text. The partner sees a broken Bible experience and has no
supported way to correct it.
The cause was our own design, not an accident. SDK CSS sat in
yv-sdk-*cascadelayers. CSS Cascade 5 §6.1
compares layer order before specificity, and unlayered CSS joins an implicit
final layer. A consumer
button {}rule at 0,0,1 thus overrode everyyv:utility at any specificity. Raising specificity inside the layer could never
help. That choice protected the consumer from us, and it guaranteed the leak in
the other direction.
There were two leak channels, not one:
button { padding: 1rem }matches our buttonbody { color: green }flows into our textcolor, so the value was inheritedAfter this PR:
typography all leave SDK components unchanged. Measured, not assumed.
!importantrule, and it is documentedwith per-component numbers.
Measured result. Baseline was 124 to 4,041 leaks per component. Four of the
five hostile groups now report 0 on every component. The fifth group,
!important, reports 880 leaks across 155<button>elements. No element thatis not a
<button>leaks anywhere.What user-facing changes did I ship
layer()import are gone.:is([data-yv-sdk], [data-yv-sdk] *). SDK CSS cannot match DOM the SDK did not render, and inside an SDK subtree our rules beat bare element selectors.font,color,line-height,letter-spacing,text-align, and the rest) plus the box model. A gate on a selector does not stop inheritance.data-yv-sdkanddata-yv-theme.FootnoteContentnamed the gate but not a theme, soyv:bg-backgroundstayed light whatever the caller asked for.How I implemented it
Five phases, in order. Each one had to move the number the phase before it
produced.
1. Measure first
Nothing was fixed here. The point was a baseline the later phases had to beat.
preflight,bareElements,aggressiveReset,inheritedTypography,important.parameters.hostileHostdecorator. A layout effect, so the injected tags always land after the hoisted SDK style tag. That is the source order a real consumer app produces.2. Stamp
data-yv-sdkon every exported rootThe gate is worthless on a root that carries no attribute.
DialogOverlaystamped. It portals todocument.body, outside every stamped ancestor.3. Declare the inherited properties
theme.css does three things:
:where([data-yv-sdk])becomes[data-yv-sdk]. The old form dropped every rule inside it to 0,0,0, which was correct while SDK CSS was meant to lose.color: inheriton every descendant, and the box model repeated on the root itself. Both are explained under Deviations.theme-inherited-properties.test.ts parses the file and asserts the set, so the block cannot be trimmed by accident.
4. Drop the layers and gate every selector
scope-selectors.mjs rewrites the compiled sheet after Tailwind runs:
It uses Lightning CSS, not a regular expression and not
postcss-prefix-selector:Selectorvisitor is a typed structural API, so it cannot corrupt an escaped class name such as.yv\:mt-4.@keyframes,@font-faceand@propertyproduce noSelectornodes, so the script skips them by construction rather than by an exclusion list.bible-reader.cssand@utility touch-hitboxboth emit nested&rules, and a prefix on a nested selector corrupts it.postcss-prefix-selector@2.1.1was tested against this repo's real output and rejected. It rewrites:hostinto[data-yv-sdk] :host, which can never match, and thus drops every theme variable without a warning.Tailwind v4 has no built-in equivalent.
prefix(yv)only renames classes,@import "tailwindcss" importanttakes no selector argument, and the v3important: '#app'option has no v4 replacement.Three build gates make the guarantee real:
dist/index.jscontains the gate string, and does not contain@layer yv-sdk-. Theyv-sdk-prefix is deliberate: Tailwind emits its own@layer propertiesblock for the@propertyfallback, whatever our directives say..cache/, so the ungated sheet can never reachdist/and be published.5. Record the decision and the residual
The residual, and the shadow DOM recommendation
preflightbareElementsaggressiveResetinheritedTypographyimportantZero means zero on every component, not an average. The 880 leaks come from two
declarations:
Recommendation: do not adopt shadow DOM. ADR-0005 fixed the condition before
the harness produced a number: recommend shadow DOM only if the residual
includes rules that do not use
!important. There is no such residual, and theresult is not close to the threshold.
The shadow DOM costs are large and are enumerated in the ADR. Radix portals
mount outside the shadow root, so
FocusScope,aria-hiddenanddismissable-layer break at the boundary. React 19
<style precedence>hoistinginto a shadow root is undocumented. Tailwind Labs state that v4 does not target
shadow DOM, because v4 depends on
@property. Theming moves per-root.If a partner reports a break from a rule that does not use
!important, theharness measures that case. Add their rule to
HOSTILE_GROUPS, run the suite,and read the number.
Deviations from the plan
Compared against
.humanlayer/tasks/.../04-structure-outline-style-isolation.md.Implemented as planned
data-yv-sdkanddata-yv-themeonprofile-avatar,separator,textarea,dialog, plusscope-attribute.test.tsx.[data-yv-sdk], withdirectionleft out on purpose.bible-reader.cssowns RTL, andtext-align: startalready follows direction.@layerremoval, the Lightning CSS rewriter (the plan already required Lightning CSS after its own verification), the.d.mtstypes, and the unit tests.:where([data-yv-sdk])to[data-yv-sdk], with the specificity comment inverted..cache/in.gitignore, the rewrittenverify-styles.js, ADR-0005, the residual report, README and AGENTS updates, and the major-bump changeset.Deviations and surprises
color: inheriton descendants and the box model on the root. Eleven of fourteen stories stayed red without both.coloron the root alone does not stop a consumerul { color }rule, which matches an SDK element directly rather than arriving by inheritance. The universal block is[data-yv-sdk] *, which misses the marked element, and a consumer* { box-sizing }does not miss it.ui/button.tsxwas never stamped, contrary to the phase 2 file list. It is not insrc/components/index.ts, and every internal<Button>call site already sits inside a stamped ancestor. Stamping it would need an audit of all 31 call sites for the correct local theme, which is out of scope for a phase whose contract was "no visual change". Two roots the plan did not name were stamped instead: the version picker's language trigger andFootnoteContent.tsconfig.build.jsonnow excludessrc/test. Not in the plan. The phase 1 harness iteratesNodeList, and the declaration build's lib (ES2020,DOM, noDOM.Iterable) cannot compile it. This surfaced only when phase 4 ran a fullpnpm build. The directory ships no public type.importantassertion changed shape. The plan asserted one global "greater than zero". The real assertion is per story, conditioned on whether the component renders a<button>.BibleCard,FootnoteContent,ProfileAvatar,SeparatorandTextarearender none, and correctly report zero.Additions not in the plan
89e5807). Comment and prose only. No selector, declaration or code path changed, and the full suite still passes.theme.cssdown to the block it describes. It had sat above the box-model block, which made it read as documentation for the wrong four declarations.Items planned but not implemented
None. All five phases have corresponding code in the diff.
How to verify it
git fetch origin git worktree add ../ype-4113-review ype-4113-spike-react-web-sdk-uidom-guarantee-consuming-app-global-css-cant-distort-bible-components-2 cd ../ype-4113-review pnpm installManual testing
pnpm --filter @youversion/platform-react-ui storybook, open Style Isolation, and look at any story. The component renders correctly under CSS built to break it.ConsumerTokenOverrideStillApplies. The supported customization path still works after the lockdown.docs/style-isolation-residual-leak.mdand check the numbers against the console output of a story run.pnpm build:css:scopefrom thebuildscript and runpnpm --filter @youversion/platform-react-ui build. The build fails.Automated tests
All of the above pass on
89e5807.Migration for consumers
Replace each consumer CSS override with a supported path:
--yv-*design tokens on[data-yv-sdk].themeprop onYouVersionProvider, or thebackgroundprop on a component.Rules that use
!importantstill win. That is the one remaining hole, anddocs/style-isolation-residual-leak.mdrecords it.Description for the changelog
Scope all SDK CSS to
data-yv-sdksubtrees, so the global CSS of a host app can no longer change SDK components.Greptile Summary
This PR introduces a CSS isolation pipeline that scopes and hardens SDK styles, protects consumer-provided slots, and adds regression coverage and migration documentation. The watcher’s startup fix still leaves a race that can miss Tailwind’s initial output.
data-yv-sdkboundaries and excludesdata-yv-slotconsumer content.remlengths topx, and verifies the generated stylesheet.Confidence Score: 4/5
The PR should not merge until the scope watcher cannot miss Tailwind’s initial stylesheet write during concurrent startup.
The missing-directory crash from the previous thread is corrected, but
runOncestill executes before watcher registration; an initial Tailwind write in that interval receives no event and no retry, leaving development or Storybook with missing or stale scoped CSS.Files Needing Attention: packages/ui/scripts/scope-selectors.mjs and packages/ui/package.json
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR A[dev or storybook] --> B[Tailwind watcher] A --> C[Scope watcher] C --> D[Initial runOnce] D -->|raw CSS missing| E[Return without output] E --> F[Register fs.watch] B -->|write occurs before F| G[Event missed] G --> H[dist/tailwind.css missing or stale]Prompt To Fix All With AI
Reviews (4): Last reviewed commit: "docs: record the :where() placement and ..." | Re-trigger Greptile
Context used: