Skip to content

Simplify CSS import strategy for React Web SDK#215

Open
cameronapak wants to merge 9 commits intomainfrom
YPE-1821-fix-css-approach
Open

Simplify CSS import strategy for React Web SDK#215
cameronapak wants to merge 9 commits intomainfrom
YPE-1821-fix-css-approach

Conversation

@cameronapak
Copy link
Copy Markdown
Collaborator

@cameronapak cameronapak commented Apr 20, 2026

Replaces the module-level injectStyles() side effect with React 19's <style href precedence> via an internal <YvStyles /> component rendered once in YouVersionProvider. Deletes inject-styles.ts, exports ./styles.css for non-React consumers, sets sideEffects: ["*.css"].

Areas changed: style injection mechanism (YvStyles + YouVersionProvider wrapper), package.json exports/sideEffects, vitest + Storybook Vite configs (__YV_STYLES__ define), global.css, README, AGENTS.md, CLAUDE.md.

Non-breaking — consumers change nothing. React 19 handles hoisting, deduplication, SSR streaming, and Suspense. Fixes FOUC on SSR, RSC incompatibility, and tree-shaking issues.

Build, typecheck, lint, and all tests pass.

Part of https://lifechurch.atlassian.net/browse/YPE-1821

Greptile Summary

Replaces the imperative injectStyles() module side-effect with a declarative <YvStyles /> component rendered once inside a new YouVersionProvider wrapper, leveraging React 19's <style href precedence> for automatic hoisting, deduplication, and SSR/Suspense support. The previously flagged P1 (__YV_STYLES__ undefined in Storybook) is resolved in this PR.

Confidence Score: 5/5

Safe to merge — all prior P1 concerns are resolved and no new blocking issues found.

The only remaining finding is a P2 comment inaccuracy in global.css. All P1 issues (Storybook ReferenceError, vitest define) are addressed. Implementation is clean and correct.

No files require special attention.

Important Files Changed

Filename Overview
packages/ui/src/lib/yv-styles.tsx New component that renders React 19 <style href precedence> with the embedded CSS constant; straightforward and correct.
packages/ui/src/components/YouVersionProvider.tsx New wrapper that composes BaseYouVersionProvider with YvStyles; JSX children correctly override spread children so no double-rendering occurs.
packages/ui/src/index.ts Removes injectStyles() side-effect call; re-exports BaseYouVersionProvider and new YouVersionProvider wrapper cleanly.
packages/ui/.storybook/main.ts Adds YV_STYLES define to viteFinal — fixes the previously flagged ReferenceError in the Storybook Vite pipeline.
packages/ui/vitest.config.ts Adds YV_STYLES define to the unit test project so tests don't throw ReferenceError when YvStyles renders.
packages/ui/package.json Adds ./styles.css export pointing at dist/tailwind.css and sideEffects: [*.css] for correct tree-shaking and non-React consumer support.
packages/ui/src/lib/inject-styles.ts Deleted — replaced by YvStyles component; removal is intentional and correct.
.changeset/replace-inject-styles-with-style-precedence.md Minor bump for all three packages per the project's unified versioning policy.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Consumer App] --> B[YouVersionProvider\npackages/ui wrapper]
    B --> C[YvStyles\nyv-styles.tsx]
    B --> D[BaseYouVersionProvider\nplatform-react-hooks]
    C --> E[React 19 hoists style tag\nto head with deduplication\nand SSR streaming support]
    D --> F[props.children rendered]
    H[Non-React Consumer] --> I[import styles.css\ndist/tailwind.css]
    subgraph Build Time
        J[tsup define injects CSS string into __YV_STYLES__]
        K[Storybook viteFinal defines __YV_STYLES__ as empty]
        L[vitest defines __YV_STYLES__ as empty]
    end
    J --> C
    K --> C
    L --> C
Loading

Fix All in Claude Code Fix All in Cursor

Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/ui/src/styles/global.css
Line: 12-14

Comment:
**Inaccurate comment — styles are not injected per-component**

The updated comment says "Each public component renders a React 19 `<style>` element", but per the implementation and `AGENTS.md`, only `YouVersionProvider` renders `<YvStyles />` — individual components do not. This contradicts the documented design and could mislead future contributors into thinking per-component style injection is the expected pattern.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (5): Last reviewed commit: "Update changeset message" | Re-trigger Greptile

Context used:

  • Rule used - Auto-injected styles: index.ts calls injectStyles(... (source)

Replace module-level injectStyles() side effect with React 19's <style precedence>
component. Each public component now renders <YvStyles /> which React handles for
hoisting, deduplication, SSR streaming, and Suspense. Fixes FOUC on SSR, RSC
incompatibility, and tree-shaking issues. Non-breaking change for consumers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 20, 2026

🦋 Changeset detected

Latest commit: f3e48c7

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-core Minor
@youversion/platform-react-hooks Minor
@youversion/platform-react-ui Minor
vite-react 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

@cameronapak cameronapak changed the title Replace injectStyles() with React 19 style precedence hoisting Simplify CSS import strategy for React Web SDK Apr 20, 2026
cameronapak and others added 2 commits April 20, 2026 13:12
Storybook tests run through Storybook's Vite dev server which needs the
__YV_STYLES__ constant defined. Storybook already loads CSS via a direct
import of dist/tailwind.css, so empty string is correct here.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@davidfedor davidfedor left a comment

Choose a reason for hiding this comment

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

Some comments from ChatGPT which seem valid to me:

(Its high level: yes this is directionally wise and fixes a real weakness; some adjustments seem wise...)

The sideEffects: false change is the biggest red flag. Since the PR also adds a ./styles.css export, webpack’s own docs strongly suggest keeping CSS in the sideEffects allowlist or bundlers may drop it. I’d keep sideEffects: ["**/*.css"] rather than blanket false (webpack tree-shaking docs).

“Non-breaking, consumers change nothing” is a little too confident. Primary React consumers probably won’t need changes, but the style-loading semantics are definitely changing, and the new styles.css path is new public surface.

The new approach creates a maintenance footgun: every public component now has to remember to render YvStyles. The old module-level injection was ugly, but impossible to forget. I’d want either a tiny guardrail test or a documented rule that every public root must include it.

The docs should explain the new model in the public README, not just the PR and AGENTS docs. Right now the package README still sells “styled included” without clarifying whether styling is injected on import, on render, or optionally via styles.css.

cameronapak and others added 6 commits April 24, 2026 09:57
…aking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rovider wrapper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ing model

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cameronapak
Copy link
Copy Markdown
Collaborator Author

Hey @davidfedor, I've looked at each of your pieces of feedback and have made relevant changes. This PR is ready for re-review and reconsideration.

The sideEffects: false change is the biggest red flag. Since the PR also adds a ./styles.css export, webpack’s own docs strongly suggest keeping CSS in the sideEffects allowlist or bundlers may drop it. I’d keep sideEffects: ["**/*.css"] rather than blanket false (webpack tree-shaking docs).

Fixed in e83614b

“Non-breaking, consumers change nothing” is a little too confident. Primary React consumers probably won’t need changes, but the style-loading semantics are definitely changing, and the new styles.css path is new public surface.

I have updated the wording of the PR description.

The new approach creates a maintenance footgun: every public component now has to remember to render YvStyles. The old module-level injection was ugly, but impossible to forget. I’d want either a tiny guardrail test or a documented rule that every public root must include it.

I've now got the <YvStyles /> component in the YouVersionProvider, so styles don't have to be added to any other components. See 1087ad9

The docs should explain the new model in the public README, not just the PR and AGENTS docs. Right now the package README still sells “styled included” without clarifying whether styling is injected on import, on render, or optionally via styles.css.

Ditto to the previous comment. It's been fixed.

@cameronapak cameronapak requested a review from davidfedor April 24, 2026 16:05
@cameronapak
Copy link
Copy Markdown
Collaborator Author

@davidfedor is this good to go?

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