Skip to content

Feat/scoped variables - #611

Open
dlebedynskyi wants to merge 7 commits into
uni-stack:mainfrom
dlebedynskyi:feat/scoped-variables
Open

Feat/scoped variables#611
dlebedynskyi wants to merge 7 commits into
uni-stack:mainfrom
dlebedynskyi:feat/scoped-variables

Conversation

@dlebedynskyi

@dlebedynskyi dlebedynskyi commented Jul 22, 2026

Copy link
Copy Markdown

@coderabbitai ignore

What

Adds <ScopedVariables> — a React Context provider that overrides CSS variables for a subtree on both web and native, the per-subtree analogue of Uniwind.updateCSSVariables (which is global-per-theme) but limited to a React tree, instead of global.

<ScopedVariables variables={{ '--color-primary': '#e11d48', '--gap': 16 }}>
  <Text className="text-(--color-primary)" />
</ScopedVariables>
  • Overrides apply only inside the subtree; nested providers merge { ...inherited, ...own }, nearest wins.
  • Keys must start with -- (dev-mode error, invalid keys dropped); values string | number, normalized by the same helper as the global API.
  • Reads back via useCSSVariable('--name'); composes with ScopedTheme / LayoutDirection.
  • Opt-in cacheKey (native only) folds a caller-supplied stable key into the native style cache; without it the subtree bypasses the cache for correctness. No-op on web.
  • Web sets the variables as inline custom properties on the display:contents wrapper so the real DOM cascade resolves them.

Context

Addresses uni-stack/uniwind#546.

Prior art

In NativeWind var serve similar purpose. Opted to have a separate explicit component, following ScopedTheme example:

import { vars } from 'nativewind'

 <View style={vars({ '--color-primary': '#e11d48' })}>
   <Text className="text-primary" />   {/* reads the scoped value */}
 </View>

Plain web - this is literally what web portion of PR does:

<div style="--color-primary: #e11d48; display:contents">
   <span style="color: var(--color-primary)">scoped</span>
 </div>

Summary by CodeRabbit

  • New Features
    • Added ScopedVariables to apply CSS custom-property overrides to a specific component subtree.
    • Nested scopes are supported, with the nearest provider winning on conflicts.
    • Works across native and web, including useCSSVariable.
  • Bug Fixes
    • Improved style caching so scoped-variable changes don’t reuse stale results.
    • Ensured scoped values update correctly when related theme or state changes occur.
  • Tests
    • Added native and web test coverage for scoped variable behavior, nesting, validation, and caching.

Dima Lebedynskyi added 4 commits July 22, 2026 13:08
Demonstrate per-subtree CSS variable overrides in the expo example app:
theme default, scoped override, nested inheritance (nearest wins), and the
opt-in cacheKey. Adds --color-primary/--color-surface/--gap theme defaults so
the unscoped baseline renders intentionally.
On web, Uniwind passes classes through RNW unchanged, so real elements
resolve `var(--name)` from the live CSS cascade. The wrapper only applied
its variables to the hidden dummyParent used for JS reads, so scoped
overrides never reached descendants — styling stayed at the theme default
while useCSSVariable readouts (which use the dummyParent path) looked
correct. Set the variables as inline custom properties on the
display:contents wrapper so they cascade to children (numbers -> px).

Add web regression tests asserting the wrapper carries the overrides
inline, nested wrappers only declare their own overrides, and invalid
keys are dropped. Rework the expo-example demo with origin pills
(default/set here/inherited), swatches, and a gap strip so the
override/inherit story is legible.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5140b86a-2045-409c-a192-3c35d1652255

📥 Commits

Reviewing files that changed from the base of the PR and between d21795e and 367cadc.

📒 Files selected for processing (2)
  • packages/uniwind/src/components/ScopedVariables/utils.ts
  • packages/uniwind/tests/native/components/scoped-variables.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/uniwind/src/components/ScopedVariables/utils.ts
  • packages/uniwind/tests/native/components/scoped-variables.test.tsx

📝 Walkthrough

Walkthrough

Adds a cross-platform ScopedVariables provider with nested merging, validation, native and web style resolution, cache-key integration, hook updates, public exports, documentation, and native/web test coverage.

Changes

ScopedVariables feature

Layer / File(s) Summary
Provider contract and context wiring
packages/uniwind/src/components/ScopedVariables/*, packages/uniwind/src/core/context.ts, packages/uniwind/src/index.ts, CONTEXT.md
Adds the ScopedVariables API, validates -- keys, merges nested values with nearest-provider precedence, derives deterministic cache keys, and exposes scoped context to descendants.
Native variable resolution and caching
packages/uniwind/src/core/native/*, packages/uniwind/src/hooks/useCSSVariable/*, packages/uniwind/tests/native/*
Normalizes native scoped values, overlays them on theme variables, incorporates scoped identity into style caching, updates hook resolution, and tests inheritance, theme interaction, reactivity, validation, and cache behavior.
Web cascade and computed-style reads
packages/uniwind/src/core/web/*, packages/uniwind/src/core/config/*, packages/uniwind/tests/web/*, packages/uniwind/tests/e2e/*
Uses a display: contents wrapper and temporary dummyParent CSS properties for scoped web reads, converts numeric values to pixels, cleans up applied properties, and updates context-shaped test fixtures.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Component
  participant ScopedVariables
  participant Runtime
  participant CSSVariableHook
  Component->>ScopedVariables: provide variables
  ScopedVariables->>Runtime: merge context and resolve styles
  Runtime->>CSSVariableHook: expose scoped variable value
  CSSVariableHook-->>Component: return resolved value
Loading

Possibly related PRs

  • uni-stack/uniwind#393: Extends the same UniwindContext and web style-resolution plumbing used for scoped theme behavior.
  • uni-stack/uniwind#521: Modifies the shared CSS-variable resolution path and useCSSVariable integration.
  • uni-stack/uniwind#577: Updates adjacent subtree-scoping and dummyParent handling in the web runtime.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding scoped variables support.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

Introduces ScopedVariables, a React Context provider that applies CSS custom-property overrides to a subtree on both web and native without touching global theme state. The implementation is well-structured, with shared validation/merging logic in utils.ts, a try/finally cleanup guarantee for the web dummyParent DOM approach, and a comprehensive test suite covering nesting, color normalization, useCSSVariable reactivity, ScopedTheme composition, and cache correctness.

  • Web: A display:contents wrapper div carries the overrides as inline custom properties so the real DOM cascade resolves them; getWebStyles / getWebVariable temporarily apply the same overrides to dummyParent for JS-side resolution, now protected by try/finally.
  • Native: The style cache key is extended with a JSON-sorted variablesCacheKey; scoped variables are overlaid onto a prototype-chained clone of the theme vars so unset variables fall through to the theme. createVarGetter is extracted from config.native.ts into a shared native-utils helper.
  • useCSSVariable: A new isMountRef guard skips the redundant updateValue() call on mount (the initial useState already captures the correct value), while context changes are handled via a [uniwindContext]-dependent useLayoutEffect.

Confidence Score: 5/5

Safe to merge; all correctness and cleanup paths are properly guarded and well-tested.

The feature is self-contained and both web and native paths are well-covered by tests. The try/finally cleanup on the DOM side is correctly in place. The two concerns flagged are limited to a DRY violation between two native-only files and a cache-entry accumulation that only materialises under frequent variable cycling with no concurrent global variable updates — neither affects correctness or typical usage.

packages/uniwind/src/core/native/store.ts and packages/uniwind/src/hooks/useCSSVariable/getVariableValue.native.ts share identical overlay-construction logic that should be extracted to a shared utility.

Important Files Changed

Filename Overview
packages/uniwind/src/components/ScopedVariables/utils.ts Shared logic for building scoped variable context: validates keys (always filters non-"--" keys, logs only in DEV), merges with ancestor variables, and computes a stable JSON-sorted cache key string.
packages/uniwind/src/core/native/store.ts Adds variablesCacheKey to the native style cache key and overlays scoped variables via prototype chaining before style resolution. The overlay construction logic is duplicated from getVariableValue.native.ts, and old cache entries from stale variablesCacheKey values accumulate until a global variable update fires.
packages/uniwind/src/core/web/getWebStyles.ts Adds applyScopedVariables to temporarily set scoped custom properties on dummyParent during JS-side style computation; both getWebStyles and getWebVariable are now wrapped in try/finally to guarantee cleanup.
packages/uniwind/src/hooks/useCSSVariable/useCSSVariable.ts Adds isMountRef optimization to skip a redundant updateValue() call on mount (useState already computes the initial value with the current context); second useLayoutEffect now depends on [uniwindContext] so scoped variable changes propagate correctly.
packages/uniwind/src/hooks/useCSSVariable/getVariableValue.native.ts Overlays scoped variables onto theme vars before variable lookup, using the same Object.create+createVarGetter pattern as store.ts resolveStyles — both sites should be extracted to a shared utility to avoid drift.
packages/uniwind/src/core/native/native-utils.ts Extracts createVarGetter (color normalization + lazy closure) from config.native.ts into a shared utility; correctly handles numbers, color strings (via culori), and plain strings.
packages/uniwind/src/core/context.ts Adds variables and variablesCacheKey fields to UniwindContext default value; backward-compatible nulls for both.
packages/uniwind/src/core/web/webUtils.ts New tiny helper toWebValue that normalises number→px for CSS custom property values, shared between updateCSSVariables and ScopedVariables on web.
packages/uniwind/tests/native/components/scoped-variables.test.tsx Comprehensive native test suite covering overrides, nesting, color normalization, useCSSVariable reactivity, ScopedTheme composition, and cache key correctness.
packages/uniwind/tests/web/components/scoped-variables.test.tsx Web test suite covering DOM cascade inheritance, inline custom property rendering, useCSSVariable reactivity, and cleanup verification via getWebVariable.

Sequence Diagram

sequenceDiagram
    participant Parent as Parent Component
    participant SV as ScopedVariables
    participant Ctx as UniwindContext
    participant Child as Child Component
    participant Store as UniwindStore (native)
    participant DOM as dummyParent (web)

    Parent->>SV: render(variables)
    SV->>SV: validateVariables(variables)
    SV->>SV: "mergedVars = spread parent + own"
    SV->>SV: "variablesCacheKey = JSON.stringify(sorted)"
    SV->>Ctx: Provider value with variables + variablesCacheKey
    Note over SV,DOM: Web only — inline custom props on display:contents div

    Child->>Ctx: useUniwindContext()
    Ctx-->>Child: ctx with variables set

    alt Native getStyles()
        Child->>Store: getStyles(className, state, ctx)
        Store->>Store: cacheKey includes variablesCacheKey
        Store->>Store: "overlay = Object.create(themeVars) + scoped getters"
        Store-->>Child: resolved RNStyle
    end

    alt Web getWebStyles()
        Child->>DOM: applyScopedVariables(ctx)
        DOM->>DOM: setProperty(name, toWebValue(val))
        DOM->>DOM: getComputedStyle → resolve
        DOM->>DOM: finally removeProperty(name)
        DOM-->>Child: resolved style object
    end

    alt useCSSVariable
        Child->>Child: useState(getCSSVariable(name, ctx))
        Child->>Child: useLayoutEffect([ctx]) isMountRef skip on mount
        Note over Child: On ctx change — updateValue() + re-subscribe
    end
Loading

Reviews (4): Last reviewed commit: "fix(ScopedVariables): use JSON.stringify..." | Re-trigger Greptile

Comment thread packages/uniwind/src/components/ScopedVariables/utils.ts Outdated
Comment thread packages/uniwind/src/core/web/getWebStyles.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/uniwind/src/components/ScopedVariables/utils.ts (1)

24-40: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Invalid-key filtering is dev-only; downstream consumers diverge in production.

validateVariables skips filtering entirely when !__DEV__ (Line 25-27), but ScopedVariables.tsx's inline-style loop always filters non--- keys regardless of environment. In a production build this means an invalid key stays in the exposed context.variables (read by getVariableValue.native.ts and applyScopedVariables in getWebStyles.ts) while never appearing as an actual inline custom property — a silent, hard-to-diagnose inconsistency between the two paths. Consider always filtering and only gating the Logger.error call behind __DEV__.

♻️ Proposed fix
 const validateVariables = (variables: CSSVariables) => {
-    if (!__DEV__) {
-        return variables
-    }
-
     return Object.fromEntries(
         Object.entries(variables).filter(([name]) => {
             if (!name.startsWith('--')) {
-                Logger.error(`CSS variable name must start with "--", instead got: ${name}`)
+                if (__DEV__) {
+                    Logger.error(`CSS variable name must start with "--", instead got: ${name}`)
+                }
 
                 return false
             }
 
             return true
         }),
     )
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/uniwind/src/components/ScopedVariables/utils.ts` around lines 24 -
40, Update validateVariables so it always removes variable names that do not
start with "--", regardless of __DEV__. Restrict only the Logger.error call to
development builds, preserving the filtered result for production consumers such
as context.variables and inline-style processing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/expo-example/ScopedVariablesDemo.tsx`:
- Around line 124-131: Update the cached subtree example in ScopedVariablesDemo,
specifically the ScopedVariables declaration with cacheKey="demo-amber", to
reuse the exact --color-primary and --gap values from section 2 while leaving
only the cacheKey difference. Keep the surrounding heading and AccentCard usage
unchanged.

In `@packages/uniwind/tests/native/components/scoped-variables.test.tsx`:
- Around line 163-182: Remove the unused `@ts-expect-error` directive from the
ScopedVariables test; the variables object already satisfies
ScopedVariablesProps and should remain unchanged so the test continues
validating the runtime warning and valid --gap styling.

In `@packages/uniwind/tests/web/components/scoped-variables.test.tsx`:
- Around line 8-31: Extend the scoped custom-property test around Probe and
ScopedVariables to update the provider’s variables prop after the initial
render, then assert that the subscribed useCSSVariable result rerenders with the
new DOM-cascaded value. Preserve the existing outside fallback assertion and
verify the inside consumer reflects both the initial and updated values.
- Around line 106-109: Remove the unused `@ts-expect-error` directive from the
invalid-keys test when rendering ScopedVariables. Keep the variables object
unchanged so the runtime filtering of the valid CSSVariables prop continues to
be tested.

---

Nitpick comments:
In `@packages/uniwind/src/components/ScopedVariables/utils.ts`:
- Around line 24-40: Update validateVariables so it always removes variable
names that do not start with "--", regardless of __DEV__. Restrict only the
Logger.error call to development builds, preserving the filtered result for
production consumers such as context.variables and inline-style processing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bcd8a38c-205e-43b8-9fea-e785e0254907

📥 Commits

Reviewing files that changed from the base of the PR and between 6cce440 and 232702e.

📒 Files selected for processing (22)
  • CONTEXT.md
  • apps/expo-example/App.tsx
  • apps/expo-example/ScopedVariablesDemo.tsx
  • apps/expo-example/global.css
  • packages/uniwind/src/components/ScopedVariables/ScopedVariables.native.tsx
  • packages/uniwind/src/components/ScopedVariables/ScopedVariables.tsx
  • packages/uniwind/src/components/ScopedVariables/index.ts
  • packages/uniwind/src/components/ScopedVariables/utils.ts
  • packages/uniwind/src/core/config/config.common.ts
  • packages/uniwind/src/core/config/config.native.ts
  • packages/uniwind/src/core/config/config.ts
  • packages/uniwind/src/core/context.ts
  • packages/uniwind/src/core/native/native-utils.ts
  • packages/uniwind/src/core/native/store.ts
  • packages/uniwind/src/core/web/getWebStyles.ts
  • packages/uniwind/src/hooks/useCSSVariable/getVariableValue.native.ts
  • packages/uniwind/src/index.ts
  • packages/uniwind/tests/consts.ts
  • packages/uniwind/tests/e2e/getWebStyles.test.ts
  • packages/uniwind/tests/native/components/scoped-variables.test.tsx
  • packages/uniwind/tests/type-test/theme.ts
  • packages/uniwind/tests/web/components/scoped-variables.test.tsx

Comment on lines +124 to +131
{/* 4. Same as override, plus an opt-in stable cacheKey (native caching) */}
<SectionHeader
title="4. Cached subtree (cacheKey)"
subtitle="Same overrides as #2, but a stable cacheKey re-enables the native style cache."
/>
<ScopedVariables variables={{ '--color-primary': '#f59e0b', '--gap': 8 }} cacheKey="demo-amber">
<AccentCard title="Amber card" primary="override" gap="override" />
</ScopedVariables>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the cache-key example’s overrides identical.

Line 127 says this is the same as section 2 except for cacheKey, but Line 129 changes both values. Reuse section 2’s values so the demo isolates cache behavior.

Proposed fix
-            <ScopedVariables variables={{ '--color-primary': '`#f59e0b`', '--gap': 8 }} cacheKey="demo-amber">
-                <AccentCard title="Amber card" primary="override" gap="override" />
+            <ScopedVariables variables={{ '--color-primary': '`#e11d48`', '--gap': 16 }} cacheKey="demo-red">
+                <AccentCard title="Cached card" primary="override" gap="override" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* 4. Same as override, plus an opt-in stable cacheKey (native caching) */}
<SectionHeader
title="4. Cached subtree (cacheKey)"
subtitle="Same overrides as #2, but a stable cacheKey re-enables the native style cache."
/>
<ScopedVariables variables={{ '--color-primary': '#f59e0b', '--gap': 8 }} cacheKey="demo-amber">
<AccentCard title="Amber card" primary="override" gap="override" />
</ScopedVariables>
{/* 4. Same as override, plus an opt-in stable cacheKey (native caching) */}
<SectionHeader
title="4. Cached subtree (cacheKey)"
subtitle="Same overrides as `#2`, but a stable cacheKey re-enables the native style cache."
/>
<ScopedVariables variables={{ '--color-primary': '`#e11d48`', '--gap': 16 }} cacheKey="demo-red">
<AccentCard title="Cached card" primary="override" gap="override" />
</ScopedVariables>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo-example/ScopedVariablesDemo.tsx` around lines 124 - 131, Update the
cached subtree example in ScopedVariablesDemo, specifically the ScopedVariables
declaration with cacheKey="demo-amber", to reuse the exact --color-primary and
--gap values from section 2 while leaving only the cacheKey difference. Keep the
surrounding heading and AccentCard usage unchanged.

Comment thread packages/uniwind/tests/native/components/scoped-variables.test.tsx
Comment thread packages/uniwind/tests/web/components/scoped-variables.test.tsx
Comment thread packages/uniwind/tests/web/components/scoped-variables.test.tsx
- useCSSVariable: recompute on context change, not only on global
  Theme/Variables events — an updated <ScopedVariables> variables prop
  (or a nearer provider) now surfaces the new value. Adds web + native
  regression tests for a prop update.
- utils: always drop non-`--` keys (not just in dev), gating only the
  Logger.error behind __DEV__, so invalid keys can't reach the web read
  helper and corrupt a resolved inheritable property in production.
- getWebStyles/getWebVariable: wrap the scoped-variable read in
  try/finally so the temporary custom properties are always cleared,
  even if a DOM read throws.
- Document the variables prop stability contract (define outside render
  or useMemo) in JSDoc.
- Remove two unused @ts-expect-error directives in tests.
- Reword the demo's cacheKey section so it no longer claims parity with
  section 2.

@Brentlok Brentlok left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for PR, I've done initial review for things that I've catched immediately, but I didn't dig any deeper into the logic yet. Since it's a huge PR and touches many core functionalities would you be okay, if I push some of my changes directly into this PR, it would be much faster in some cases than writing a detailed comments?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We're not really adding any new features into expo-example, it's just a playground when developing new features, we're actually planning to minimize this screen. Our regression tests should take care of any feature

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm ok to remove it, just needed some space to visually test. lmk what would you prefer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can keep it for now, and remove it in the end, after we're done with this new feature

)

return (
<View style={{ display: 'contents' }}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think that we need this in .native

* Caching only engages when every ancestor `<ScopedVariables>` in the merge
* chain also opted in; if any ancestor bypasses, this subtree bypasses too.
*/
cacheKey?: string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't really like requesting from the user an additional key that he may not fully understand. We could build our custom cacheKey inside ScopedVariables

const cacheKey = useMemo(
    () =>
        Object
            .entries(variables)
            .sort(([a], [b]) => a.localeCompare(b))
            .reduce((acc, [key, value]) => `${acc}${key}:${value};`, ''),
    [variables],
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I thought about doing that as well. Reasons why not was just the cost of compute for key - hence opted-in to allow user provide a custom key, where they can do above on their side.

If you prefer that way, this is an easy change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think that the cost of not having a style cache is bigger that computing this cacheKey, so let's generate this cache key

const names = Object.keys(uniwindContext.variables)

Object.entries(uniwindContext.variables).forEach(([name, value]) => {
dummyParent.style.setProperty(name, typeof value === 'number' ? `${value}px` : value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We're using typeof value === 'number' ? ${value}px : value in more and more places, let's move this to some kind of web only util

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agree. I saw several places, but did not want to touch unrelated code.

// Recompute on context change too: a new `uniwindContext` (e.g. an
// updated `<ScopedVariables>` prop or a nearer provider) can resolve to
// a different value without any global Theme/Variables event firing.
updateValue()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't this cause an extra rerender on mount?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Initially I did not have it but this was flagged by one of auto reviewers as potential staleness.

Comment on lines +31 to +34

// ScopedVariables variables prop
type ScopedVariablesProp = ComponentProps<typeof ScopedVariables>['variables']
type ScopedVariablesPropTest = Expect<Equal<ScopedVariablesProp, Record<string, string | number>>>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We don't need this, we're testing here if Theme has correct type light | dark | etc. instead of plain string
This just forces updating tests if the API changes

@Brentlok Brentlok left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There's too much long, multi line comments everywhere, it's not common in this repo

@dlebedynskyi

dlebedynskyi commented Jul 23, 2026

Copy link
Copy Markdown
Author

@Brentlok

if I push some of my changes directly into this PR, it would be much faster in some cases than writing a detailed comments?

please go ahead.

There's too much long, multi line comments everywhere, it's not common in this repo
Can you to what are you referring to here?



- Derive the native style cache key from the merged variables map instead
  of a user-supplied cacheKey prop; removes the cache-bypass path and the
  stale-key footgun
- Drop the display: contents View wrapper on native, render the bare
  provider like ScopedTheme.native
- Remove the expo-example demo (playground only, covered by tests)
- useCSSVariable: skip the mount-time recompute, useState already
  resolved the initial value
- Extract toWebValue (number -> px) web util, reuse in config,
  getWebStyles and the web wrapper
- Remove the ScopedVariables prop type test
- Trim long comments to match repo style

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/uniwind/src/components/ScopedVariables/utils.ts`:
- Around line 32-35: Update the variablesCacheKey serialization in the
mergedVariables cache-key construction to use an unambiguous encoding that
cannot collide when variable keys or values contain delimiters such as
semicolons or colons. Preserve deterministic ordering of entries so equivalent
variable maps produce the same key, and keep the resulting key compatible with
its use by UniwindStore.getStyles.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cd7d602a-9e01-4195-b622-fe5160ee43d9

📥 Commits

Reviewing files that changed from the base of the PR and between c34ed83 and d21795e.

📒 Files selected for processing (13)
  • CONTEXT.md
  • packages/uniwind/src/components/ScopedVariables/ScopedVariables.native.tsx
  • packages/uniwind/src/components/ScopedVariables/ScopedVariables.tsx
  • packages/uniwind/src/components/ScopedVariables/utils.ts
  • packages/uniwind/src/core/config/config.ts
  • packages/uniwind/src/core/native/native-utils.ts
  • packages/uniwind/src/core/native/store.ts
  • packages/uniwind/src/core/web/getWebStyles.ts
  • packages/uniwind/src/core/web/index.ts
  • packages/uniwind/src/core/web/webUtils.ts
  • packages/uniwind/src/hooks/useCSSVariable/useCSSVariable.ts
  • packages/uniwind/tests/native/components/scoped-variables.test.tsx
  • packages/uniwind/tests/web/components/scoped-variables.test.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/uniwind/src/core/native/native-utils.ts
  • packages/uniwind/src/core/config/config.ts
  • packages/uniwind/src/core/web/getWebStyles.ts

Comment thread packages/uniwind/src/components/ScopedVariables/utils.ts Outdated
@dlebedynskyi

Copy link
Copy Markdown
Author

@Brentlok
I've done a pass on your comments. Please take a look. Feel free to update PR as needed.

The key:value; concatenation had no escaping, so values containing
separators could collide ({'--a': '1;--b:2'} vs {'--a': '1', '--b': '2'})
and serve wrong cached styles.
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