Open
Conversation
Introduce the resolvable expression API, payload normalization and serialization in @use-voltra/core, wire it through the renderer and stylesheet registry, and implement evaluation on iOS (parser, evaluator, payload migration). Add Swift and Node tests, an example iOS resolvable playground widget, and use a realm-safe plain-object check so Expo prerender works with VM-evaluated styles. Made-with: Cursor
Replace AndroidDynamicColors (~ string literals) with the same env/when/match API as iOS. Extend RESOLVABLE_ENV_IDS with Material role keys; authors use env.primary and siblings from voltra/android. Android parses payloads through a Kotlin resolvable evaluator after decompression; Material env ids resolve to existing ~ tokens for JSColorParser and GlanceTheme-backed Dynamic colors. iOS accepts the new env ids but resolves them to null outside Android. Re-export resolvable helpers and types from @use-voltra/android and voltra; AndroidColorValue is ResolvableValue<string>. Update examples and tests. Made-with: Cursor
…g comment Export isResolvableCondition from normalize.ts and import it in serialize.ts instead of maintaining an identical (but weaker-typed) local copy. Also remove a comment in VoltraElement.swift that narrated the code rather than explaining a non-obvious constraint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tries - Add Android Glance renderers (ControlFlowRenderers.kt) for ControlIf and ControlSwitch, dispatched from RenderElement/RenderElementWithModifier - Expose ResolvableValueEvaluator.evaluateCondition() for renderer use; props are pre-resolved by ResolvablePayloadResolver so no env needed at render - Add ControlIf/ControlSwitch to ios-server component registry (IDs 22/23) - Fix android-server registry: AndroidControlIf=20, AndroidControlSwitch=21, AndroidChart=22 (was incorrectly mapped to 20, shadowing the control-flow IDs) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
What is this?
Widgets and Live Activities render a payload that is computed once on the JS side and then handed off to the native layer. This works well for static content, but breaks down for properties that depend on the rendering environment — things like whether iOS is showing your widget in accented or full-color mode, or which Material color tokens Android is currently using for its dynamic theme.
Previously, the only way to handle these was to register multiple pre-rendered variants and hope the right one was picked, or to ignore the platform context entirely and hard-code colors. Neither is a real solution.
How does it work?
This PR introduces resolvable values — expressions that are embedded directly in a widget payload and evaluated by the native layer at render time, against the actual runtime environment. The JS side describes what the value should be, and the native side resolves what it actually is at the moment the widget is drawn.
The API is a small set of composable primitives exported from
voltra:These can be used anywhere a style property or prop value is accepted. The expressions are serialized into the widget payload as compact opcode tuples and resolved natively — no JS re-render, no round trip.
Supported environment keys
iOS:
renderingMode—'accented','fullColor', or'vibrant'showsWidgetContainerBackground—trueorfalseAndroid:
primary,onPrimary,surface,onSurface, etc.)Why is this useful?
when(eq(env.renderingMode, 'accented'), ...)you adapt per-render.showsWidgetContainerBackgroundtells you whether the system is drawing a widget background. You can conditionally add padding or a border only when there is no background.Example: iOS playground widget
example/widgets/ios/IosResolvablePlaygroundWidget.tsxdemonstrates the full API in a real widget. It renders a grid of indicators that highlight which rendering mode and background state are currently active — each indicator's border width and color is driven entirely by resolvable expressions:The widget is registered under the
resolvable_playgroundwidget ID and all three size variants (systemSmall,systemMedium,systemLarge) are exported asresolvablePlaygroundVariants.🤖 Generated with Claude Code