Skip to content

feat(openui): impl renderer#2600

Merged
gaoachao merged 5 commits into
mainfrom
feat/openui-impl-renderer
May 11, 2026
Merged

feat(openui): impl renderer#2600
gaoachao merged 5 commits into
mainfrom
feat/openui-impl-renderer

Conversation

@gaoachao
Copy link
Copy Markdown
Collaborator

@gaoachao gaoachao commented May 11, 2026

Summary by CodeRabbit

  • New Features

    • Added OpenUI component library (Button, Card, CardHeader, Stack, Separator, Tag, TextContent), renderer, playground entry, and form/validation hooks for interactive demos.
    • Playground: streaming parse/render preview and improved demo URL shortening + copy feedback.
  • Documentation

    • Added guidance for new packages to include package-level tsconfig and be referenced by the genui tsconfig.
  • Chores

    • Updated linter to ignore generated OpenUI build artifacts.

Review Change Stack

Summary

  • add the new package with renderer, runtime hooks, component catalog, and styles
  • wire to preview OpenUI scenarios through a Lynx app entry and demo page updates
  • add GenUI tsconfig wiring plus a repository instruction so new GenUI packages are included in type-aware linting

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 11, 2026

🦋 Changeset detected

Latest commit: aae162a

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46df39a1-f568-4581-a46a-9ce7aa8969e3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new @lynx-js/openui-reactlynx package implementing OpenUI catalog components, a React renderer, context/hooks for streaming parsing and state, form validation, CSS styling, and integrates the package into the a2ui-playground with demo scenarios and improved URL-copy/shortening UX.

Changes

OpenUI Library Core & Catalog

Layer / File(s) Summary
Package & TypeScript Setup
packages/genui/openui/package.json, packages/genui/openui/tsconfig.json, packages/genui/tsconfig.json, .github/genui-tsconfig.instructions.md, biome.jsonc
New package export map and package-level tsconfig; added project reference to genui; updated genui tsconfig instruction and Biome lint ignore for openui.
Playground Entry & App
packages/genui/a2ui-playground/lynx-src/openui/index.tsx, packages/genui/a2ui-playground/lynx-src/openui/App.tsx, packages/genui/a2ui-playground/lynx-src/openui/index.css
New Lynx entrypoint mounting App; App creates an OpenUI library, streams scenario parsing, manages loading/error UI, and renders OpenUiRenderer; CSS adds page padding + box-sizing.
Playground Build & Mock Data
packages/genui/a2ui-playground/lynx.config.ts, packages/genui/a2ui-playground/package.json, packages/genui/a2ui-playground/lynx-src/openui/mockData.ts, packages/genui/a2ui-playground/src/mock/openui-scenarios.ts
Added openui build entry; added workspace dependency @lynx-js/openui-reactlynx; introduces OpenUIScenario and OPENUI_SCENARIOS raw payload; expands pricing-cards parsed payload to a fully inlined element tree with updated texts and meta.
Public Core API & Library Factory
packages/genui/openui/src/core/index.ts, packages/genui/openui/src/core/createLibrary.tsx
Re-exports parser helpers and types from @openuidev/lang-core; adds createOpenUiLibrary() registering catalog components and component groups.
React Library Types
packages/genui/openui/src/core/library.tsx
React-specific type aliases and wrappers: ComponentRenderProps, ComponentRenderer, DefinedComponent, Library/LibraryDefinition, plus React-bound defineComponent/createLibrary.
Catalog Utilities & Action Schema
packages/genui/openui/src/catalog/utils.ts, packages/genui/openui/src/catalog/Action/index.tsx
GAP_CLASS map and asArray helper; LegacyActionConfig interface, ActionLike union, Zod schemas for action-plan and legacy actions, exported actionPropSchema.
Layout Components
packages/genui/openui/src/catalog/Stack/index.tsx, packages/genui/openui/src/catalog/Card/index.tsx
Stack with direction/wrap/gap/align/justify props and class composition; Card with variant/layout props rendering children via renderNode.
Content & Button Components
packages/genui/openui/src/catalog/TextContent/index.tsx, packages/genui/openui/src/catalog/CardHeader/index.tsx, packages/genui/openui/src/catalog/Tag/index.tsx, packages/genui/openui/src/catalog/Separator/index.tsx, packages/genui/openui/src/catalog/Button/index.tsx, packages/genui/openui/src/catalog/index.ts
TextContent, CardHeader, Tag, Separator components; Button implements action normalization, optional form validation gating, and Buttons group; catalog barrel re-exports components.
Key Utility
packages/genui/openui/src/core/utils.ts
keyFrom generates stable-ish keys from primitives, arrays, and objects (special-casing element-like objects).
Renderer
packages/genui/openui/src/core/renderer.tsx
OpenUiRenderer renders parsed result.root via RenderNode, manages internal form state, provides getFieldValue/setFieldValue, and forwards actions via onAction; includes render helpers and type guards.
Hooks & State Management
packages/genui/openui/src/core/hooks/*
useOpenUIState central hook: streaming parser, Store init, QueryManager, evaluationContext, prop evaluation, triggerAction handling (legacy + ActionPlan), error aggregation; useStateField, useFormValidation, and hooks barrel added.
Styling & CSS Module
packages/genui/openui/src/core/renderer.css, packages/genui/openui/src/core/renderer.css.d.ts
Renderer CSS with stack/layout utilities, card/button/text/tag styles, and .dark theme overrides; .d.ts stub for CSS imports.

Playground Integration & UX

Layer / File(s) Summary
OpenUIDemosPage enhancements
packages/genui/a2ui-playground/src/pages/OpenUIDemosPage.tsx
Adds renderCopied/renderCopyFailed state for QR-copy feedback; doRender resets feedback flags; attempts to shorten native and web render URLs by POSTing rawText to rspeedy __openui_payload and rewrites URLs when rawTextUrl returns; copy button reports success/failure and auto-resets feedback state.
Build & Dependency
packages/genui/a2ui-playground/lynx.config.ts, packages/genui/a2ui-playground/package.json
Wires openui entry into Lynx build and adds @lynx-js/openui-reactlynx workspace dependency.
Misc
.changeset/cuddly-moons-hide.md
Blank changeset file added.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • HuJean
  • Sherry-hue
  • PupilTong

Poem

🐰 I scurried in code, stitched Stack and Card,

Buttons that hop, and text not hard.
Parsers stream steady, hooks mind the state,
Demo blooms bright — hop over and celebrate!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(openui): impl renderer' clearly describes the main feature addition—implementing an OpenUI renderer. It accurately reflects the primary objective shown in the PR summary and corresponds to the significant new renderer implementation at packages/genui/openui/src/core/renderer.tsx.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/openui-impl-renderer

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/genui/a2ui-playground/src/mock/openui-scenarios.ts (1)

62-623: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep raw and parsed synchronized for this scenario

The updated parsed tree now describes a different product matrix (e.g., Starter/$9 and new CTA texts), while raw still defines Free/$0 and different action prompts. This fixture now contradicts itself and can mask parser regressions or confuse side-by-side preview/debug flows. Please regenerate parsed from raw (or update raw to match exactly).

🤖 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/genui/a2ui-playground/src/mock/openui-scenarios.ts` around lines 62
- 623, The fixture's parsed JSON and raw definitions are out of sync: the parsed
object (key "parsed", including statementIds like "starterAmount", "proAmount",
"enterpriseAmount", "starterBtn", "proBtn", "enterpriseBtn") reflects a
Starter/Pro/Enterprise matrix with $9/$29/$99 and new CTA texts, while the raw
source still defines Free/$0 and different actions; regenerate the parsed value
from the raw scenario (or vice‑versa) so that the "raw" and "parsed" entries are
identical representations of the same scenario, then replace the current parsed
JSON with the freshly generated output ensuring statementId values and action
strings match exactly.
packages/genui/openui/src/core/renderer.css (1)

1-272: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Configure Stylelint to recognize the rpx unit.

Stylelint reports 37 errors for "Unknown unit 'rpx'". The rpx (responsive pixel) unit is a standard length unit in Lynx and WeChat Mini Programs (screen standardized to 750 rpx), so these errors are false positives.

Update your Stylelint configuration to whitelist rpx:

⚙️ Suggested Stylelint config fix

Add to your .stylelintrc.json:

{
  "rules": {
    "unit-no-unknown": [
      true,
      {
        "ignoreUnits": ["rpx"]
      }
    ]
  }
}
🤖 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/genui/openui/src/core/renderer.css` around lines 1 - 272, Stylelint
is flagging the Lynx/WeChat-specific unit "rpx" as unknown; update the Stylelint
rules by adding an exception for the unit (referencing the "unit-no-unknown"
rule) so "rpx" is whitelisted and the 37 false positives go away; modify your
Stylelint config (e.g., .stylelintrc.json) to include "ignoreUnits": ["rpx"]
under "unit-no-unknown" so the CSS using "rpx" (seen throughout OpenUI classes
like .OpenUIStack, .OpenUICard, .OpenUIButton, etc.) no longer triggers errors.
🧹 Nitpick comments (7)
packages/genui/openui/src/core/utils.ts (2)

45-51: 💤 Low value

Simplify boolean handling—condition always true.

For boolean values, the string 'true' or 'false' will always have length > 0, making the condition on line 47 always true and the return on line 50 unreachable. Consider simplifying the logic.

♻️ Suggested simplification
 if (t === 'boolean') {
   const str = value ? 'true' : 'false';
-  if (str && str.length > 0) {
-    return appendFallback(str, getFallback());
-  }
-  return getFallback();
+  return appendFallback(str, getFallback());
 }
🤖 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/genui/openui/src/core/utils.ts` around lines 45 - 51, The boolean
branch in the t === 'boolean' block currently checks str.length which is always
>0 for 'true'/'false' making the fallback return unreachable; simplify by
removing the redundant length check and always return appendFallback(str,
getFallback()) (use the existing local variables value, str, appendFallback, and
getFallback) so the unreachable getFallback() return is eliminated.

4-6: ⚡ Quick win

Replace deprecated .substr() with .slice().

The .substr() method is deprecated. Use .slice() instead for modern JavaScript compatibility.

♻️ Suggested fix
 function generateRandomKey(): string {
-  return `key-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+  return `key-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
 }
🤖 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/genui/openui/src/core/utils.ts` around lines 4 - 6, The
generateRandomKey function uses the deprecated .substr() on the
Math.random().toString(36) result; replace .substr(2, 9) with .slice(2, 11) (or
equivalent slice indices) in generateRandomKey so the random segment length
remains the same and avoids deprecated API usage.
packages/genui/openui/src/core/hooks/useFormValidation.ts (1)

84-91: 💤 Low value

Consider using Object.hasOwn() for more precise property checks.

The 'value' in value check inspects the entire prototype chain. For defensive coding, consider using Object.hasOwn(value, 'value') to check only own properties, though this is unlikely to cause issues with controlled form state.

♻️ Suggested refinement
 let value = reg.getValue();
 // Normalize: form state stores { value, componentType }; extract actual value if needed
 if (
   value != null
   && typeof value === 'object'
-  && 'value' in value
-  && 'componentType' in value
+  && Object.hasOwn(value, 'value')
+  && Object.hasOwn(value, 'componentType')
 ) {
   value = (value as { value: unknown }).value;
 }
🤖 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/genui/openui/src/core/hooks/useFormValidation.ts` around lines 84 -
91, The property checks in useFormValidation.ts currently use the in-operator on
the variable named value (lines showing "'value' in value" and "'componentType'
in value"), which inspects the prototype chain; change these to use
Object.hasOwn(value, 'value') and Object.hasOwn(value, 'componentType') so only
own properties are considered. Update the conditional inside the
useFormValidation hook (the block that casts value to { value: unknown }) to
replace both "'value' in value" and "'componentType' in value" with
Object.hasOwn checks to make the guard more defensive and precise.
packages/genui/openui/src/core/hooks/useOpenUIState.ts (1)

132-149: 💤 Low value

Mutating refs inside useMemo is an anti-pattern.

parseExceptionRef.current = … is assigned both during the memo body and on the error branch. useMemo callbacks are expected to be pure; under Strict Mode the memo can run twice during development and React 19's compiler optimizations can rerun memoized work, which would (a) clobber a captured exception with null on a benign re-eval and (b) double-record exceptions. Consider moving the side-effect to a useEffect, or computing both result and error from useMemo and storing them in adjacent refs from a single effect.

🤖 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/genui/openui/src/core/hooks/useOpenUIState.ts` around lines 132 -
149, The code mutates parseExceptionRef.current inside the useMemo that computes
result (symbols: parseExceptionRef, useMemo, result, sp.set, response) which is
impure; change this by making useMemo return both the parse result and any parse
error (e.g., { result, error }) or only compute the pure result there, and then
move the assignment to parseExceptionRef.current into a separate useEffect that
watches [response, sp] (or assign both refs together in one effect) so
side-effects are performed outside the memoized pure computation and
double-evaluations in Strict Mode won’t clobber or double-record exceptions.
packages/genui/openui/src/core/renderer.tsx (1)

201-235: ⚡ Quick win

triggerAction passes wrapped { value, componentType } entries to onAction.

relevantState is built directly from formStateRef.current, which stores FieldEntry wrappers. The parallel useOpenUIState implementation unwraps values before evaluation/eventing. Consumers binding to event.formState will need to know about the wrapper here but not when using useOpenUIState, which is a footgun. Consider unwrapping (or at least documenting the wire format) so both code paths emit the same shape.

🤖 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/genui/openui/src/core/renderer.tsx` around lines 201 - 235,
triggerAction is passing the raw FieldEntry wrappers from formStateRef.current
into the onAction handler (via relevantState), causing a mismatch with
useOpenUIState which unwraps values; update triggerAction (the handler built
inside useCallback) to unwrap form values before building relevantState by
mapping each entry in formStateRef.current (and the single formName entry) to
its .value (preserving undefined/null if present) so onActionRef.current
receives the same plain value shape as useOpenUIState, and keep the existing
formName and params logic intact.
packages/genui/openui/src/catalog/Button/index.tsx (1)

44-48: ⚡ Quick win

Clarify the 'steps' property check.

The condition !('steps' in action) filters the action but lacks context. What does the presence of steps indicate? Is this distinguishing between legacy and new action formats?

Adding a comment would help maintainers understand when actions have steps and why they should be excluded from the legacy path.

📝 Suggested clarification
  const onTap = () => {
+   // Legacy actions don't have 'steps'; filter to handle only simple action configs
    const legacyAction: LegacyActionConfig | undefined =
      action && !('steps' in action)
        ? action
        : undefined;
🤖 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/genui/openui/src/catalog/Button/index.tsx` around lines 44 - 48, The
check inside onTap that sets legacyAction = action && !('steps' in action) ?
action : undefined is unclear; add a concise inline comment above or next to
this logic explaining that the presence of a 'steps' property denotes the newer
multi-step action format (so actions with steps should follow the new flow), and
that this branch is explicitly selecting legacy single-step actions for the
legacy handling path (reference the onTap function and legacyAction variable to
locate the code).
packages/genui/a2ui-playground/lynx-src/openui/App.tsx (1)

28-42: 💤 Low value

Consider async parsing pattern for consistency.

The synchronous parsing wrapped in try/catch works for the current mock data scenario. However, if createStreamingParser().push() might perform I/O or become async in the future, wrapping it in setLoading(true) suggests async intent.

Consider whether the parsing should be explicitly async to match the loading state management pattern, or document that parsing is intentionally synchronous.

🤖 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/genui/a2ui-playground/lynx-src/openui/App.tsx` around lines 28 - 42,
Wrap the synchronous parse in an explicit async flow or document intent: change
the parsing block in App.tsx that uses createStreamingParser() and
streamParser.push(mockData) to an async pattern (setLoading(true) before
awaiting the parse, use "const result = await streamParser.push(mockData)"
inside try/catch/finally, then call setParseResult, setError and
setLoading(false) guarded by cancelled) so it matches the loading state
handling; if streamParser.push is intentionally synchronous, add a concise
comment next to createStreamingParser/streamParser.push explaining it is
synchronous and will not perform I/O so that setLoading is not misleading.
🤖 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/genui/openui/src/catalog/Button/index.tsx`:
- Around line 109-116: ButtonsRenderer is declared with explicit parameter types
that conflict with the framework's expected ComponentRenderer signature;
refactor the Buttons component to declare its renderer inline as an arrow
function inside defineComponent (matching how Stack and Card do it) so it
receives renderNode via ComponentRenderProps, or alternatively change the
ButtonsRenderer type to ComponentRenderer<{ buttons: unknown[] }>. Update the
defineComponent call for Buttons to use the inline arrow renderer pattern (or
adjust the ButtonsRenderer signature) and ensure props remain z.object({
buttons: z.array(Button.ref) }) so the component compiles with the framework's
render type.

In `@packages/genui/openui/src/catalog/Card/index.tsx`:
- Line 67: The code uses dot access on an index-signature typed object
(GAP_CLASS.m) which violates strict TS rules; change the fallback to bracket
notation (use GAP_CLASS['m']) so the expression becomes GAP_CLASS[gap] ??
GAP_CLASS['m']; do the same replacement wherever GAP_CLASS.m appears (e.g., in
Card index and Stack index) and ensure you use bracket access for any other
index-signature properties.

In `@packages/genui/openui/src/catalog/index.ts`:
- Around line 7-9: The barrel exports in this module use .jsx extensions while
the actual component files are .tsx, causing an inconsistency; update each
export line (e.g., the exported symbols Separator, Stack, Tag and also Button,
Card, CardHeader, TextContent) to reference the .tsx files instead of .jsx so
the paths match the real files and tooling that requires exact extensions can
resolve them correctly.

In `@packages/genui/openui/src/catalog/Tag/index.tsx`:
- Line 4: Update the non-standard import of Zod by replacing the subpath import
"zod/v4" with the standard "zod" so the exported symbol z is imported
consistently with the rest of the codebase; locate the top-level import of z
(import { z } from 'zod/v4') in Tag's module and change it to import { z } from
'zod' to match the project's Zod v3 dependency.

In `@packages/genui/openui/src/core/hooks/useOpenUIState.ts`:
- Around line 41-51: The function unwrapFieldValue uses property access .value
after checking `'value' in (v as Record<string, unknown>)`, which violates
noPropertyAccessFromIndexSignature; update the access to bracket form so the
checked index-signature is accessed safely: replace occurrences of (v as
Record<string, unknown>).value with (v as Record<string, unknown>)['value']
inside unwrapFieldValue (keeping the same type cast and guard logic).
- Around line 14-26: Remove the unused QuerySnapshot type from the type import
list in useOpenUIState.ts (the import that currently lists ActionEvent,
ActionPlan, EvalContext, EvaluationContext, OpenUIError, ParseResult,
QueryManager, QuerySnapshot, Store, ToolProvider) so the import no longer
includes QuerySnapshot; this eliminates the unused-import error while keeping
all other imported types intact.

In `@packages/genui/openui/src/core/renderer.tsx`:
- Around line 174-199: The setFieldValue callback in renderer.tsx currently
renames and ignores the shouldTriggerSaveCallback flag; update OpenUiRenderer to
accept and propagate an onStateUpdate prop and restore the parameter (remove
leading underscore) in setFieldValue so when shouldTriggerSaveCallback is true
you call onStateUpdate(newState) (or await/handle it if async) after updating
formStateRef and setFormState; ensure callers like useSetDefaultValue and the
context interface (referenced in context.tsx lines ~51-54) observe the
persistence semantics. If you prefer not to persist, remove the parameter from
setFieldValue and update the context/type docs and all call sites (e.g.,
useSetDefaultValue) to reflect that this renderer only manages in-memory state.
- Around line 241-260: The context is populating store and evaluationContext
with inert empty objects which misleads consumers (contextValue { store,
evaluationContext })—change this to honestly represent absence by passing null
(i.e. store: null, evaluationContext: null) or implement a clear branch/flag in
OpenUiRenderer and adjust the OpenUIContextValue type so store and
evaluationContext are Store | null and EvaluationContext | null; update usages
like useStateField to rely on the existing ?? null fallback and document the
limitation if expression evaluation is unsupported.

---

Outside diff comments:
In `@packages/genui/a2ui-playground/src/mock/openui-scenarios.ts`:
- Around line 62-623: The fixture's parsed JSON and raw definitions are out of
sync: the parsed object (key "parsed", including statementIds like
"starterAmount", "proAmount", "enterpriseAmount", "starterBtn", "proBtn",
"enterpriseBtn") reflects a Starter/Pro/Enterprise matrix with $9/$29/$99 and
new CTA texts, while the raw source still defines Free/$0 and different actions;
regenerate the parsed value from the raw scenario (or vice‑versa) so that the
"raw" and "parsed" entries are identical representations of the same scenario,
then replace the current parsed JSON with the freshly generated output ensuring
statementId values and action strings match exactly.

In `@packages/genui/openui/src/core/renderer.css`:
- Around line 1-272: Stylelint is flagging the Lynx/WeChat-specific unit "rpx"
as unknown; update the Stylelint rules by adding an exception for the unit
(referencing the "unit-no-unknown" rule) so "rpx" is whitelisted and the 37
false positives go away; modify your Stylelint config (e.g., .stylelintrc.json)
to include "ignoreUnits": ["rpx"] under "unit-no-unknown" so the CSS using "rpx"
(seen throughout OpenUI classes like .OpenUIStack, .OpenUICard, .OpenUIButton,
etc.) no longer triggers errors.

---

Nitpick comments:
In `@packages/genui/a2ui-playground/lynx-src/openui/App.tsx`:
- Around line 28-42: Wrap the synchronous parse in an explicit async flow or
document intent: change the parsing block in App.tsx that uses
createStreamingParser() and streamParser.push(mockData) to an async pattern
(setLoading(true) before awaiting the parse, use "const result = await
streamParser.push(mockData)" inside try/catch/finally, then call setParseResult,
setError and setLoading(false) guarded by cancelled) so it matches the loading
state handling; if streamParser.push is intentionally synchronous, add a concise
comment next to createStreamingParser/streamParser.push explaining it is
synchronous and will not perform I/O so that setLoading is not misleading.

In `@packages/genui/openui/src/catalog/Button/index.tsx`:
- Around line 44-48: The check inside onTap that sets legacyAction = action &&
!('steps' in action) ? action : undefined is unclear; add a concise inline
comment above or next to this logic explaining that the presence of a 'steps'
property denotes the newer multi-step action format (so actions with steps
should follow the new flow), and that this branch is explicitly selecting legacy
single-step actions for the legacy handling path (reference the onTap function
and legacyAction variable to locate the code).

In `@packages/genui/openui/src/core/hooks/useFormValidation.ts`:
- Around line 84-91: The property checks in useFormValidation.ts currently use
the in-operator on the variable named value (lines showing "'value' in value"
and "'componentType' in value"), which inspects the prototype chain; change
these to use Object.hasOwn(value, 'value') and Object.hasOwn(value,
'componentType') so only own properties are considered. Update the conditional
inside the useFormValidation hook (the block that casts value to { value:
unknown }) to replace both "'value' in value" and "'componentType' in value"
with Object.hasOwn checks to make the guard more defensive and precise.

In `@packages/genui/openui/src/core/hooks/useOpenUIState.ts`:
- Around line 132-149: The code mutates parseExceptionRef.current inside the
useMemo that computes result (symbols: parseExceptionRef, useMemo, result,
sp.set, response) which is impure; change this by making useMemo return both the
parse result and any parse error (e.g., { result, error }) or only compute the
pure result there, and then move the assignment to parseExceptionRef.current
into a separate useEffect that watches [response, sp] (or assign both refs
together in one effect) so side-effects are performed outside the memoized pure
computation and double-evaluations in Strict Mode won’t clobber or double-record
exceptions.

In `@packages/genui/openui/src/core/renderer.tsx`:
- Around line 201-235: triggerAction is passing the raw FieldEntry wrappers from
formStateRef.current into the onAction handler (via relevantState), causing a
mismatch with useOpenUIState which unwraps values; update triggerAction (the
handler built inside useCallback) to unwrap form values before building
relevantState by mapping each entry in formStateRef.current (and the single
formName entry) to its .value (preserving undefined/null if present) so
onActionRef.current receives the same plain value shape as useOpenUIState, and
keep the existing formName and params logic intact.

In `@packages/genui/openui/src/core/utils.ts`:
- Around line 45-51: The boolean branch in the t === 'boolean' block currently
checks str.length which is always >0 for 'true'/'false' making the fallback
return unreachable; simplify by removing the redundant length check and always
return appendFallback(str, getFallback()) (use the existing local variables
value, str, appendFallback, and getFallback) so the unreachable getFallback()
return is eliminated.
- Around line 4-6: The generateRandomKey function uses the deprecated .substr()
on the Math.random().toString(36) result; replace .substr(2, 9) with .slice(2,
11) (or equivalent slice indices) in generateRandomKey so the random segment
length remains the same and avoids deprecated API usage.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db87fcce-6aa8-4678-aa67-1fd712ea1b20

📥 Commits

Reviewing files that changed from the base of the PR and between ad1f90f and bd34486.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • .github/genui-tsconfig.instructions.md
  • biome.jsonc
  • packages/genui/a2ui-playground/lynx-src/openui/App.tsx
  • packages/genui/a2ui-playground/lynx-src/openui/index.css
  • packages/genui/a2ui-playground/lynx-src/openui/index.tsx
  • packages/genui/a2ui-playground/lynx-src/openui/mockData.ts
  • packages/genui/a2ui-playground/lynx.config.ts
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui-playground/src/mock/openui-scenarios.ts
  • packages/genui/a2ui-playground/src/pages/OpenUIDemosPage.tsx
  • packages/genui/openui/package.json
  • packages/genui/openui/src/catalog/Action/index.tsx
  • packages/genui/openui/src/catalog/Button/index.tsx
  • packages/genui/openui/src/catalog/Card/index.tsx
  • packages/genui/openui/src/catalog/CardHeader/index.tsx
  • packages/genui/openui/src/catalog/Separator/index.tsx
  • packages/genui/openui/src/catalog/Stack/index.tsx
  • packages/genui/openui/src/catalog/Tag/index.tsx
  • packages/genui/openui/src/catalog/TextContent/index.tsx
  • packages/genui/openui/src/catalog/index.ts
  • packages/genui/openui/src/catalog/utils.ts
  • packages/genui/openui/src/core/context.tsx
  • packages/genui/openui/src/core/createLibrary.tsx
  • packages/genui/openui/src/core/hooks/index.ts
  • packages/genui/openui/src/core/hooks/useFormValidation.ts
  • packages/genui/openui/src/core/hooks/useOpenUIState.ts
  • packages/genui/openui/src/core/hooks/useStateField.ts
  • packages/genui/openui/src/core/index.ts
  • packages/genui/openui/src/core/library.tsx
  • packages/genui/openui/src/core/openuiLibrary.ts
  • packages/genui/openui/src/core/renderer.css
  • packages/genui/openui/src/core/renderer.css.d.ts
  • packages/genui/openui/src/core/renderer.tsx
  • packages/genui/openui/src/core/utils.ts
  • packages/genui/openui/tsconfig.json
  • packages/genui/tsconfig.json

Comment thread packages/genui/openui/src/catalog/Button/index.tsx
Comment thread packages/genui/openui/src/catalog/Card/index.tsx Outdated
Comment thread packages/genui/openui/src/catalog/index.ts
Comment thread packages/genui/openui/src/catalog/Tag/index.tsx
Comment thread packages/genui/openui/src/core/hooks/useOpenUIState.ts
Comment thread packages/genui/openui/src/core/hooks/useOpenUIState.ts
Comment thread packages/genui/openui/src/core/renderer.tsx
Comment thread packages/genui/openui/src/core/renderer.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/genui/openui/src/catalog/Action/index.tsx`:
- Around line 26-36: legacyActionSchema currently allows an entirely empty
object so invalid action-plan-like payloads can slip through the union; update
legacyActionSchema (the schema used in actionPropSchema next to
actionPlanSchema) to require at least one of its fields instead of accepting
all-optional: define the same fields but add a refinement that ensures
Object.keys(input).length > 0 (or use a z.union of required alternatives) so an
empty object or malformed `{ steps: [...] }` won't validate via the legacy
fallback, then keep actionPropSchema as the union of actionPlanSchema and this
hardened legacyActionSchema.

In `@packages/genui/openui/src/core/renderer.css`:
- Around line 101-103: The transparent clear-card rule (.OpenUICardVariantClear)
is being overridden by the dark-theme rule that sets a background on
.OpenUICard; update the selectors so clear cards win in all themes by adding a
more specific override for the variant (e.g., target
.OpenUICard.OpenUICardVariantClear and also .dark
.OpenUICard.OpenUICardVariantClear) and set background-color: transparent (or
transparent !important if necessary) so the dark-theme background does not apply
to CardVariantClear; adjust the CSS around the .OpenUICard and
.OpenUICardVariantClear rules to include these more specific selectors.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3ee65595-d180-4d38-a6f8-d4130468ac6e

📥 Commits

Reviewing files that changed from the base of the PR and between bd34486 and 315f224.

📒 Files selected for processing (7)
  • packages/genui/openui/src/catalog/Action/index.tsx
  • packages/genui/openui/src/catalog/Button/index.tsx
  • packages/genui/openui/src/catalog/Card/index.tsx
  • packages/genui/openui/src/catalog/Stack/index.tsx
  • packages/genui/openui/src/core/renderer.css
  • packages/genui/openui/src/core/renderer.tsx
  • packages/genui/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/genui/tsconfig.json
  • packages/genui/openui/src/catalog/Card/index.tsx
  • packages/genui/openui/src/catalog/Button/index.tsx
  • packages/genui/openui/src/catalog/Stack/index.tsx
  • packages/genui/openui/src/core/renderer.tsx

Comment thread packages/genui/openui/src/catalog/Action/index.tsx
Comment thread packages/genui/openui/src/core/renderer.css
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 11, 2026

Merging this PR will degrade performance by 14.96%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 2 regressed benchmarks
✅ 79 untouched benchmarks
⏩ 26 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
002-hello-reactLynx-destroyBackground 863.4 µs 912.4 µs -5.37%
transform 1000 view elements 40 ms 47.1 ms -14.96%

Comparing feat/openui-impl-renderer (aae162a) with main (d588d03)2

Open in CodSpeed

Footnotes

  1. 26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (ad1f90f) during the generation of this report, so d588d03 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 11, 2026

React Example with Element Template

#282 Bundle Size — 197.79KiB (0%).

aae162a(current) vs d588d03 main#263(baseline)

Bundle metrics  Change 1 change
                 Current
#282
     Baseline
#263
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 79 79
No change  Duplicate Modules 23 23
Change  Duplicate Code 40.32%(-0.02%) 40.33%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#282
     Baseline
#263
No change  IMG 145.76KiB 145.76KiB
No change  Other 52.03KiB 52.03KiB

Bundle analysis reportBranch feat/openui-impl-rendererProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 11, 2026

React External

#1131 Bundle Size — 690.27KiB (0%).

aae162a(current) vs d588d03 main#1112(baseline)

Bundle metrics  no changes
                 Current
#1131
     Baseline
#1112
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#1131
     Baseline
#1112
No change  Other 690.27KiB 690.27KiB

Bundle analysis reportBranch feat/openui-impl-rendererProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 11, 2026

React Example

#8017 Bundle Size — 235.77KiB (0%).

aae162a(current) vs d588d03 main#7998(baseline)

Bundle metrics  no changes
                 Current
#8017
     Baseline
#7998
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 197 197
No change  Duplicate Modules 80 80
No change  Duplicate Code 44.85% 44.85%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#8017
     Baseline
#7998
No change  IMG 145.76KiB 145.76KiB
No change  Other 90.01KiB 90.01KiB

Bundle analysis reportBranch feat/openui-impl-rendererProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 11, 2026

Web Explorer

#9590 Bundle Size — 900.04KiB (0%).

aae162a(current) vs d588d03 main#9570(baseline)

Bundle metrics  no changes
                 Current
#9590
     Baseline
#9570
No change  Initial JS 44.46KiB 44.46KiB
No change  Initial CSS 2.22KiB 2.22KiB
Change  Cache Invalidation 0% 13.59%
No change  Chunks 9 9
No change  Assets 11 11
No change  Modules 229 229
No change  Duplicate Modules 11 11
No change  Duplicate Code 27.28% 27.28%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#9590
     Baseline
#9570
No change  JS 495.91KiB 495.91KiB
No change  Other 401.92KiB 401.92KiB
No change  CSS 2.22KiB 2.22KiB

Bundle analysis reportBranch feat/openui-impl-rendererProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 11, 2026

React MTF Example

#1148 Bundle Size — 206.6KiB (0%).

aae162a(current) vs d588d03 main#1128(baseline)

Bundle metrics  no changes
                 Current
#1148
     Baseline
#1128
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 192 192
No change  Duplicate Modules 77 77
No change  Duplicate Code 44.36% 44.36%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#1148
     Baseline
#1128
No change  IMG 111.23KiB 111.23KiB
No change  Other 95.37KiB 95.37KiB

Bundle analysis reportBranch feat/openui-impl-rendererProject dashboard


Generated by RelativeCIDocumentationReport issue

@gaoachao gaoachao merged commit b770f9c into main May 11, 2026
53 of 54 checks passed
@gaoachao gaoachao deleted the feat/openui-impl-renderer branch May 11, 2026 13:16
upupming added a commit that referenced this pull request May 11, 2026
Pick up the new `packages/genui/openui` package added by #2600 and
dedupe a stack of `@rsbuild/core@2.0.0-beta.3` indirect deps that
sherif flagged.
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