Skip to content

feat: add A2UI playground init#2472

Merged
Sherry-hue merged 1 commit intolynx-family:mainfrom
Sherry-hue:feat/a2ui-playground
Apr 24, 2026
Merged

feat: add A2UI playground init#2472
Sherry-hue merged 1 commit intolynx-family:mainfrom
Sherry-hue:feat/a2ui-playground

Conversation

@Sherry-hue
Copy link
Copy Markdown
Collaborator

@Sherry-hue Sherry-hue commented Apr 17, 2026

feat: add playground new style

A2UI Playground (Init)

What

This PR introduces an A2UI Playground (under packages/genui/a2ui-playground) built with Rsbuild, to quickly explore, debug, and preview A2UI protocol payloads and component behaviors on Web, with an easy path to preview on mobile devices.

UI Overview

AI Chat

image
  • Chat UI scaffold for describing the UI you want to build
  • Placeholder preview panel on the right (Lynx Preview)
  • Protocol switcher (0.8 / 0.9) in the top bar

Demos

image
  • Scenario selector (left)
  • JSON editor (center) with Reset / Clear / Render
  • Lynx Preview (right) + “View on Device” QR code for mobile preview

Components

image
  • Browse A2UI components by category
  • Component detail pages with:
    • Usage snippets (per protocol version)
    • Props table (name / type / description / default)

How To Run

  • pnpm --filter a2ui-playground dev
  • Open the dev server URL and use the Protocol toggle (0.8 / 0.9) to compare behaviors.

Follow-ups (Needs To Be Completed)

  1. Demos
    • Provide a real/public DEFAULT_DEMO_URL or a bundled demo artifact (currently empty, so rendering depends on follow-up work)
    • Improve render status UX (loading / empty / render failed), and error feedback for invalid payloads
  2. AI Chat
    • Connect to a real LLM/service to generate A2UI JSON from user input
    • Render generated output in Lynx Preview and enable QR-based “open on device”
  3. Components
    • Expand component catalog coverage and protocol-diff documentation
    • Add quick actions like “Copy JSON”, “Open in Demos”, etc.

Notes

This PR focuses on establishing the Playground structure and core UX layout; functional completeness will be delivered incrementally in follow-up PRs.

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).

Summary by CodeRabbit

  • New Features

    • Interactive A2UI Playground: browsable component catalog, per-component docs, usage examples, hash-based navigation, and Lynx-backed preview rendering.
    • Demo library: static examples and editable dynamic scenarios with live preview, QR sharing, and mobile simulator.
    • AI Chat sandbox with protocol switch and editable JSON scenarios.
    • Compact UI widgets: chips, QR viewer, protocol control, and mobile preview.
  • Improvements

    • Theming, responsive layout, preview styling, and flexible weight-based layout sizing.
    • Many new mock demos and utilities for URL/encoding and render URL generation.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 17, 2026

⚠️ No Changeset found

Latest commit: 1d913db

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

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

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@Sherry-hue Sherry-hue force-pushed the feat/a2ui-playground branch from 5155489 to cd4495c Compare April 17, 2026 08:24
@gaoachao gaoachao self-requested a review April 17, 2026 08:25
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new private ESM package packages/genui/a2ui-playground implementing a React/Lynx playground (build configs, entries, pages, components, mocks, demos, utilities, styles, and Lynx app) and applies localized A2UI catalog tweaks (Column/Row/List/Image exports/weight handling).

Changes

Cohort / File(s) Summary
Playground manifest & workspace
packages/genui/a2ui-playground/package.json, pnpm-workspace.yaml, packages/genui/a2ui-playground/tsconfig.json
New private package manifest, scripts (rsbuild/rspeedy), dependencies/devDeps, and per-package tsconfig; adds packages/genui/* to workspace globs.
Build & Lynx configs
packages/genui/a2ui-playground/rsbuild.config.ts, packages/genui/a2ui-playground/lynx.config.ts
Adds rsbuild config (React plugin, entries, publicDir/www, dev server) and lynx rspeedy config (pluginQRCode schema, pluginReactLynx, build output).
App entries & bootstraps
packages/genui/a2ui-playground/src/entry.tsx, packages/genui/a2ui-playground/src/render.tsx, packages/genui/a2ui-playground/src/App.tsx
Adds React & render entry points and main App with hash routing, protocol switch, and mount logic.
Pages
packages/genui/a2ui-playground/src/pages/*
Adds Home, AIChatPage, ComponentsPage, DemosPage, Dynamic (DynamicPage), Static; preview/editor, QR/render URL flows.
UI components
packages/genui/a2ui-playground/src/components/*
Adds presentational components: Chip, MobilePreview, ProtocolSwitch, QrCode (async generation), UsageSection.
Catalog, demos & mocks
packages/genui/a2ui-playground/src/componentCatalog.ts, packages/genui/a2ui-playground/src/demos.ts, packages/genui/a2ui-playground/src/mock/messages/*
Typed component catalog, STATIC_DEMOS and mock message streams (cast-grid, citywalk-list, recs, fridge-search, trip-planner, workout-plan).
Mock runtime & globals
packages/genui/a2ui-playground/src/mock/EventSource.ts, packages/genui/a2ui-playground/src/globals.d.ts
Adds test-only EventSource mock installer with actionMocks routing and ambient/global type declarations (flags, CSS modules).
Lynx-side app
packages/genui/a2ui-playground/lynx-src/*
Adds Lynx bundle source: App (streams mock actions), entry, CSS, and lynx-src tsconfig for the Lynx-rendered variant.
Utilities
packages/genui/a2ui-playground/src/utils/*
Adds base64url encode/decode, DEFAULT_DEMO_URL, protocol type/normalizer, and buildRenderUrl that encodes messages/actionMocks into query params.
Styles
packages/genui/a2ui-playground/src/styles.css
Adds comprehensive playground CSS (theme tokens, layout, pages, responsive rules).
A2UI catalog tweaks
packages/genui/a2ui/src/catalog/Column/index.tsx, packages/genui/a2ui/src/catalog/Row/index.tsx, packages/genui/a2ui/src/catalog/List/index.tsx, packages/genui/a2ui/src/catalog/Image/style.css, packages/genui/a2ui/src/catalog/all.ts
Column/Row accept legacy alias props and wrap weighted children; List dynamic-template handling adjusted; Image CSS for weighted containers; barrel exports updated to .js entry paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • HuJean
  • PupilTong
  • gaoachao

Poem

🐰 I hopped in the code with a curious glance,
Demos and mocks began their dance,
QR seeds glint, previews hum and play,
Lynx and React together sway,
I nibble a carrot — build it today! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.32% 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: add A2UI playground init' accurately describes the main change—introducing a new A2UI Playground package with initial setup and UI infrastructure.
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

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 Apr 17, 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: 11

🧹 Nitpick comments (17)
pnpm-workspace.yaml (1)

27-27: Nit: keep packages/* entries grouped/sorted.

The existing packages/* globs are loosely grouped together (lines 4–25); appending packages/genui/* after website breaks that grouping. Consider moving it next to the other packages/* entries for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pnpm-workspace.yaml` at line 27, Move the "packages/genui/*" glob so it sits
with the other "packages/*" entries (i.e., alongside the existing package globs)
and reorder/group the "packages/*" lines alphabetically/consistently so all
package globs remain contiguous; update the list to keep the "packages/*"
entries together and sorted, ensuring "packages/genui/*" is placed among them
rather than after "website".
packages/genui/a2ui-playground/src/mock/messages/0.9/action_book.json (1)

1-14: Consider adding a comment to document that this fixture depends on a pre-existing surface.

The fixture is safe as-is: card.json (which creates surface_sh_restaurants) is always loaded first as the default initial data, and action_book.json is only dispatched when a USER_ACTION is detected in EventSource.ts. By design, the surface exists before these update commands run.

However, this dependency isn't obvious from the fixture file alone. A brief comment in action_book.json (e.g., "Requires card.json to be loaded first to create surface_sh_restaurants") would improve clarity for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/mock/messages/0.9/action_book.json` around
lines 1 - 14, Add a one-line comment at the top of action_book.json stating that
this fixture requires the surface "surface_sh_restaurants" to already exist
(created by card.json) before the update commands run; reference that
action_book.json is dispatched on USER_ACTION in EventSource.ts so maintainers
understand the load order and dependency.
packages/genui/a2ui-playground/src/components/MobilePreview.tsx (1)

12-12: Optional: consider adding sandbox and loading="lazy" to the preview iframe.

The iframe embeds arbitrary render URLs (including potentially user-entered demoUrl values passed through buildRenderUrl). Adding a sandbox attribute with the minimum required tokens (e.g. allow-scripts allow-same-origin) would limit the blast radius if a non-trusted URL is ever previewed, and loading="lazy" avoids loading the preview before it's visible.

♻️ Suggested change
-          <iframe className='phoneIframe' title='preview' src={props.src} />
+          <iframe
+            className='phoneIframe'
+            title='preview'
+            src={props.src}
+            loading='lazy'
+            sandbox='allow-scripts allow-same-origin allow-forms'
+          />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/components/MobilePreview.tsx` at line 12,
The iframe in MobilePreview (the element rendering props.src) embeds arbitrary
URLs and should be hardened and deferred: update the iframe rendered in
MobilePreview to include a restrictive sandbox attribute (e.g., "allow-scripts
allow-same-origin" or narrower depending on required features) and add
loading="lazy" to defer offscreen loading; keep the existing title/className but
ensure any required allow-* tokens match the preview features you need (adjust
sandbox tokens in the iframe created in MobilePreview to avoid breaking
functionality).
packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx (1)

9-26: Consider aria-pressed for toggle semantics.

For a protocol toggle, adding aria-pressed={value === '0.x'} on each button (or switching to role="radio" inside a role="radiogroup") would make the active state announced correctly by assistive tech — the current active class is only a visual cue.

Proposed change
       <button
         type='button'
+        aria-pressed={value === '0.8'}
         className={value === '0.8' ? 'protocolButton active' : 'protocolButton'}
         onClick={() =>
           onChange('0.8')}
       >
         v0.8
       </button>
       <button
         type='button'
+        aria-pressed={value === '0.9'}
         className={value === '0.9' ? 'protocolButton active' : 'protocolButton'}
         onClick={() =>
           onChange('0.9')}
       >
         v0.9
       </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx` around
lines 9 - 26, The buttons in ProtocolSwitch (the div with role='group' and the
two button elements using value and onChange) currently use only a visual
'protocolButton active' class to indicate selection; add proper toggle semantics
by setting aria-pressed on each button (e.g., aria-pressed={value === '0.8'} for
the v0.8 button and aria-pressed={value === '0.9'} for v0.9) or alternatively
convert the container to role='radiogroup' and each button to role='radio' with
aria-checked driven by value, ensuring the active state is exposed to assistive
tech while keeping existing onClick/onChange and className logic intact.
packages/genui/a2ui-playground/package.json (1)

23-23: Redundant self-alias on @types/react.

"@types/react": "npm:@types/react@^19.2.14" aliases the package to itself; the npm: prefix adds no value here and is typically only used when aliasing a different package name. Simplify to a plain version range for clarity and consistency with @types/react-dom on the next line.

♻️ Proposed change
-    "@types/react": "npm:`@types/react`@^19.2.14",
+    "@types/react": "^19.2.14",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/package.json` at line 23, The dependency entry
uses a redundant self-alias: update the package.json dependency key
"@types/react" to remove the "npm:" prefix and use a normal semver version range
(e.g. change from "npm:`@types/react`@^19.2.14" to a plain "^19.2.14") so it
matches the style of "@types/react-dom" and avoids unnecessary aliasing.
packages/genui/a2ui-playground/src/App.tsx (1)

26-34: Normalize empty componentName to undefined.

For a hash like #/components/ (trailing slash) or #/components//foo, parts[1] is an empty string rather than undefined, which then propagates as componentName="" to ComponentsPage. Harmless today (lookup fails and falls through), but it's cleaner to normalize here:

♻️ Proposed change
   if (parts[0] === 'components') {
-    return { tab: 'components', componentName: parts[1] };
+    return { tab: 'components', componentName: parts[1] || undefined };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/App.tsx` around lines 26 - 34, The
parseHash function can return componentName as an empty string for hashes like
"#/components/" or "#/components//foo"; update parseHash so when parts[0] ===
'components' it normalizes parts[1] to undefined if it's missing or an empty
string (e.g., set componentName to parts[1] || undefined or explicitly check
parts.length>1 && parts[1] !== ''), so ComponentsPage always receives either a
non-empty string or undefined.
packages/genui/a2ui-playground/src/globals.d.ts (1)

14-14: Consider an explicit CSS module shape if/when CSS Modules are used.

The empty declare module '*.css' {} works for side-effect imports (import './styles.css'). If you later adopt CSS Modules, you'll want a typed default export:

♻️ Suggested shape for CSS Modules
-declare module '*.css' {}
+declare module '*.css' {
+  const content: { readonly [className: string]: string };
+  export default content;
+}

Safe to defer — current usage is side-effect-only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/globals.d.ts` at line 14, The current
empty declaration "declare module '*.css' {}" only supports side-effect imports;
to prepare for future CSS Modules usage update the declaration for the module
pattern '*.css' to export a typed default (e.g., a CSS module mapping or
interface) so CSS imports have a typed default export; modify the declaration in
globals.d.ts (the "declare module '*.css'" block) to export a default object
type (like Record<string,string> or a named interface such as CSSModule) so both
side-effect and CSS Module imports are type-safe.
packages/genui/a2ui-playground/src/pages/AIChatPage.tsx (2)

32-34: Add a dependency array to the auto-scroll effect.

Without dependencies, scrollIntoView fires on every render (including parent re-renders caused by protocol changes or input typing, not just when messages changes). Scope it to message updates.

Proposed fix
-  useEffect(() => {
-    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
-  });
+  useEffect(() => {
+    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+  }, [messages]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/AIChatPage.tsx` around lines 32 -
34, The useEffect that calls messagesEndRef.current?.scrollIntoView({ behavior:
'smooth' }) runs on every render because it has no dependency array; update the
effect in AIChatPage by adding a dependency array that includes the messages
state (e.g., useEffect(..., [messages])) so the auto-scroll only triggers when
messages change; keep the messagesEndRef reference inside the effect and ensure
messages is the correct variable name used in the component.

25-27: Unused protocol prop.

_props is never read. Either wire protocol into the mock response / preview logic (expected per PR follow-up #2) or drop the prop until AI generation is connected to avoid a misleading API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/AIChatPage.tsx` around lines 25 -
27, The component AIChatPage currently takes _props: { protocol: ProtocolVersion
} but never uses the protocol prop; either wire it into the mock
response/preview logic (use the protocol value inside AIChatPage to select/mock
responses or pass it down to Preview/MockResponse components) or remove the prop
to avoid a misleading API; specifically update the AIChatPage function signature
(remove _props and its type) and any callers, or reference props.protocol within
the mock generation code (the places that build the preview or mockResponse) so
the ProtocolVersion is actually consumed.
packages/genui/a2ui-playground/src/render.tsx (1)

109-118: Reload effect runs unconditionally on mount with empty state.

When no query params are present, initial is null, so on first mount this assigns lynxView.initData = {} and calls reload() before any real init arrives via postMessage. Consider guarding to skip the null/empty case so the lynx-view doesn't do a wasted reload cycle.

Proposed fix
   useEffect(() => {
     const lynxView = lynxViewRef.current;
-    if (!lynxView) return;
-
-    lynxView.initData = initData ?? {};
-
-    if (typeof lynxView.reload === 'function') {
-      lynxView.reload();
-    }
+    if (!lynxView || !initData) return;
+
+    lynxView.initData = initData;
+    lynxView.reload?.();
   }, [initData]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/render.tsx` around lines 109 - 118, The
effect currently sets lynxView.initData = initData ?? {} and calls
lynxView.reload() unconditionally, causing a needless reload when initData is
null/empty; update the useEffect to early-return unless initData is non-null and
non-empty (e.g., check initData != null and Object.keys(initData).length > 0),
then assign lynxView.initData and call lynxView.reload() only in that case —
reference lynxViewRef.current, lynxView.initData and lynxView.reload when making
the change.
packages/genui/a2ui-playground/src/mock/mock-data-manager-08.ts (1)

49-51: API diverges from getActionMocks09.

The 0.9 version accepts an optional action parameter and returns a filtered single-entry map when provided. This 0.8 version always returns the full actionMockMap, which will silently ignore that argument if callers upgrade or share helpers. Mirror the 0.9 signature for consistency.

Proposed fix
-export function getActionMocks08() {
-  return actionMockMap;
-}
+export function getActionMocks08(action?: string) {
+  if (action && actionMockMap[action]) {
+    return { [action]: actionMockMap[action] };
+  }
+  return actionMockMap;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/mock/mock-data-manager-08.ts` around lines
49 - 51, getActionMocks08 currently always returns the full actionMockMap;
change its signature to match getActionMocks09 by accepting an optional action
parameter (e.g., action?: string) and, when action is provided, return a new map
containing only the entry for that action (or an empty map if not found);
otherwise return the full actionMockMap. Update the function name
getActionMocks08 and internal logic to perform the conditional filtering so
callers expecting the 0.9 behavior receive a single-entry map when passing an
action.
packages/genui/a2ui-playground/src/demos.ts (1)

260-264: Inconsistent variant field on some 0.9 Text entries.

cta-text (line 262–264) and btn-text (line 371–375) omit variant, while other Text nodes in the same demos use variant: 'body'/'h2'/'caption'. Either add variant: 'body' explicitly for consistency, or rely on the default and remove it everywhere.

Also applies to: 371-375

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/demos.ts` around lines 260 - 264, The Text
demo entries with id 'cta-text' and 'btn-text' are missing a variant while other
Text nodes use variants like 'body'/'h2'/'caption'; update those two objects
(ids 'cta-text' and 'btn-text') to include variant: 'body' so their shape
matches the other Text entries (or alternatively normalize all Text entries by
removing explicit variants—pick one approach and apply uniformly).
packages/genui/a2ui-playground/src/mock/EventSource.ts (1)

19-27: Listener registration race on very first scheduled event.

emitSequence is started from the constructor. It awaits a 10 ms timer before the first emit, so in practice callers that addEventListener synchronously after new EventSource(...) are fine — but this is fragile and not how the real EventSource behaves. Consider deferring the initial setTimeout by using queueMicrotask(() => this.emitSequence()) and adding a small "have any listeners yet" guard, or documenting the contract that listeners must be attached synchronously after construction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/mock/EventSource.ts` around lines 19 - 27,
The constructor-started emitSequence has a 10ms initial delay causing a race
where listeners added synchronously may miss the very first event; change the
constructor to schedule emitSequence via queueMicrotask(() =>
this.emitSequence()) instead of calling it directly, and inside emitSequence (or
a helper like hasListenersGuard) check whether any listeners (registered via
addEventListener) exist before emitting the first 'delta'—if none, wait (e.g.,
short loop with microtask/yield) until a listener is present or a small timeout
elapses; update references to emitSequence, constructor, and addEventListener
accordingly so the first event is reliably delivered to listeners.
packages/genui/a2ui-playground/src/mock/mock-data-manager-09.ts (1)

58-61: Parameter type could drop the explicit undefined.

mock: string | undefined can be simplified to mock?: string for consistency with getActionMocks09 (line 51). Nitpick.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/mock/mock-data-manager-09.ts` around lines
58 - 61, Change the function signature of getMockData09 to use an optional
parameter (mock?: string) instead of mock: string | undefined to match
getActionMocks09; update the declaration of getMockData09 and any internal
references to the parameter (resolveMockKey09(mock)) to use the new optional
parameter type while leaving the body returning mockDataMap[mockKey] ??
(cardMessages09 as unknown[]).
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)

53-71: Raw String(e) in user-facing error.

setError(\Invalid JSON: ${String(e)}`)surfaces something likeInvalid JSON: SyntaxError: Unexpected token …. Using e instanceof Error ? e.message : String(e)` gives a cleaner message. Minor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 53 - 71,
The catch in doRender currently uses String(e) when calling setError, which
yields verbose raw Error objects; update the catch to extract a clean message (e
instanceof Error ? e.message : String(e)) and pass that to setError in place of
String(e); if TypeScript complains, type the catch parameter as unknown or
narrow it with the instanceof check before using .message; keep this change
inside the doRender function around the JSON.parse try/catch.
packages/genui/a2ui-playground/src/pages/Dynamic.tsx (1)

68-74: Prefer e instanceof Error ? e.message : String(e).

Same note as DemosPage.tsx: String(e) yields a noisy SyntaxError: … prefix. Minor consistency nit across both pages.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx` around lines 68 - 74,
In the catch block after JSON.parse in Dynamic.tsx, change the error formatting
to use the error message only; replace the current setError call that uses
String(e) with logic like e instanceof Error ? e.message : String(e) so setError
receives just the error message (refer to the catch(e) block and the
setError(...) call to locate where to update).
packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx (1)

57-109: Deduplicate category filtering between ComponentGrid and ComponentsPage.

ComponentGrid filters COMPONENT_CATALOG per-category on every render, while the parent already computes groupedByCategory (lines 102–109). Consider passing groupedByCategory down (or hoisting a shared helper/constant) so the grouping is done once.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx` around lines 57
- 109, ComponentGrid duplicates category filtering already done in
ComponentsPage; pass the precomputed grouping instead of re-filtering. Modify
ComponentsPage to pass groupedByCategory (the Map produced from CATEGORIES and
COMPONENT_CATALOG) as a prop to ComponentGrid (or export a shared helper that
builds the Map) and update ComponentGrid to accept that prop and iterate the Map
entries instead of calling COMPONENT_CATALOG.filter for each category; reference
the ComponentGrid and groupedByCategory symbols and remove the inline filtering
logic that uses COMPONENT_CATALOG.filter/CATEGORIES inside ComponentGrid.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-playground/src/mock/EventSource.ts`:
- Around line 12-51: The MockEventSourceBase currently never stops emitSequence
and lacks standard EventSource APIs; add an internal boolean like "closed" (or
an AbortController) to MockEventSourceBase, set it in close(), and check it
inside emitSequence (and before/after each await) to bail out and avoid
scheduling further timers; also implement removeEventListener(type, listener) to
remove a listener from listeners, add readyState property updated when
constructed/closed, and expose onmessage/onerror/onopen setters that
register/remove corresponding listeners so consumers using those surfaces behave
like an EventSource (update emit to call both registered listeners and on*
callbacks).

In `@packages/genui/a2ui-playground/src/mock/messages/0.8/card.json`:
- Line 356: The literalString value currently uses template tokens
("literalString": "评分:{{rating}} · 区域:{{area}}") which will not be interpolated;
update the component to use data binding by removing the template tokens and
instead bind to a precomputed field (displayRatingArea) from the model—replace
the literalString usage with a text binding like { "text": { "path":
"displayRatingArea" } } and ensure displayRatingArea is populated from rating
and area in the data model before rendering.

In
`@packages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_numbers_booleans.json`:
- Around line 9-39: Rename or document the fixture to make its purpose explicit
(e.g., indicate mixed absolute/relative paths) and add an explicit test
expectation for boolean→Text coercion: update the "updateDataModel" / surface
"surface_data_binding_numbers_booleans_09" fixture to include expected output
metadata asserting the Text component bound at "path": "/active" (component id
"active-text", component "Text") renders the coerced string (e.g., "true" or the
canonical casing your runtime uses) and similarly assert the number rendering
for "count" ("count-text"); ensure the expectation key (e.g., "expectedOutput"
or "testExpectations") is present alongside the existing
updateDataModel/updateComponents so test runners can verify coercion behavior.

In `@packages/genui/a2ui-playground/src/mock/mock-data-manager-08.ts`:
- Around line 53-56: getMockData08 currently falls back to actionBook08 when
mockDataMap[mockKey] is missing, which yields an action-response payload instead
of a full rendering sequence; change the fallback to use cardMessages09 (like
the 0.9 counterpart) so the renderer always receives a coherent sequence. Update
the return expression in getMockData08 to return mockDataMap[mockKey] ??
(cardMessages09 as unknown[]) and ensure resolveMockKey08, mockDataMap, and
cardMessages09 are referenced unchanged.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 73-78: The effect is using ALL_SCENARIOS[0] instead of the
selected scenario and doesn't update the editor state on protocol change; update
the useEffect to read from currentScenario (not ALL_SCENARIOS[0]), include
currentScenario in the dependency array, and when protocol changes compute const
json = formatJson(currentScenario.messagesByProtocol[protocol]) and both call
doRender(json, currentScenario) and update the editor state by calling
setCustomJson(json) (or the equivalent setter for customJson) so the preview and
editor stay in sync; if you want to preserve edits, gate the setCustomJson call
to only run when the editor currently matches the previous protocol's formatted
JSON.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx`:
- Around line 29-32: customJson is initialized once from
DYNAMIC_PRESETS[0]?.messagesByProtocol[protocol] via formatJson but never
updates when protocol changes, so users who switch protocol see stale JSON;
update logic to reset customJson when protocol changes unless the user has
already edited it: add a flag (e.g., isCustomEdited) and set it to true on user
edits, and add an effect that listens to protocol (useEffect(() => {...},
[protocol])) which computes the new formatted value via
formatJson(DYNAMIC_PRESETS[0]?.messagesByProtocol[protocol] ?? []) and calls
setCustomJson only if isCustomEdited is false (or if current customJson equals
the old formatted value), ensuring formatJson, DYNAMIC_PRESETS, customJson,
setCustomJson and protocol are used to locate the change.

In `@packages/genui/a2ui-playground/src/pages/Home.tsx`:
- Line 27: The hero chip list in Home.tsx is hardcoded and includes unsupported
names ('Form', 'Progress', 'Accordion'); switch it to derive its items from the
canonical SUPPORTED_COMPONENTS export (or a curated subset) from
packages/genui/a2ui-playground/src/demos.ts so the UI cannot drift. Update the
map that currently iterates over ['Card','Form','List','Progress','Accordion']
to instead import SUPPORTED_COMPONENTS (or a filtered array like
SUPPORTED_COMPONENTS.filter(...)) and map over that result, ensuring only
supported component names are rendered.

In `@packages/genui/a2ui-playground/src/render.tsx`:
- Around line 34-79: parseInitDataFromQuery is extracting and setting
messagesUrl and actionMocksUrl on the returned InitData even though they are
never used downstream; either remove these unused fields from the InitData
interface and stop reading them from URLSearchParams (remove
params.get('messagesUrl') and params.get('actionMocksUrl') and the corresponding
properties in the initData object) OR, if they are intended to be consumed, add
a clear TODO comment and implement/fork the fetch logic (e.g., fetch and parse
JSON from messagesUrl/actionMocksUrl when provided) and document which component
will consume them; reference parseInitDataFromQuery, InitData, messagesUrl and
actionMocksUrl when making the change.

In `@packages/genui/a2ui-playground/src/styles.css`:
- Around line 895-902: The .chatMessage CSS rule uses the deprecated property
word-break: break-word; replace that declaration with overflow-wrap: anywhere
(or overflow-wrap: break-word if you prefer broader compatibility) inside the
.chatMessage rule, removing the word-break line so the class uses overflow-wrap
for word wrapping instead.
- Around line 25-30: Change the font-family keywords in the CSS custom
properties --geist-font and --geist-mono to lowercase for system/generic
identifiers (e.g., BlinkMacSystemFont → blinkmacsystemfont, Roboto → roboto,
Oxygen → oxygen, Ubuntu → ubuntu, Cantarell → cantarell, SFMono-Regular →
sfmono-regular, Menlo → menlo, Consolas → consolas) while leaving quoted font
family names (e.g., "Segoe UI", "Fira Sans", "Geist Mono") unchanged; update the
declarations inside --geist-font and --geist-mono accordingly so they comply
with the stylelint value-keyword-case rule.

In `@packages/genui/a2ui-playground/src/utils/demoUrl.ts`:
- Line 4: DEFAULT_DEMO_URL is an empty string and consumers must guard against
falsy/empty values before using it; update consuming code (e.g. DemosPage.tsx
and render.tsx) to short-circuit whenever DEFAULT_DEMO_URL or any derived
demoUrl is falsy: disable the Render button/QR, render an empty-state UI, and
avoid calling fetch, new URL(...), or setting an iframe/src with an empty
string. Locate uses of DEFAULT_DEMO_URL and any functions that build render URLs
(e.g. renderDemo, generateRenderUrl, or direct new URL(...) calls) and add a
simple guard (if (!demoUrl) { showEmptyState/disableRender; return }) so no
network or iframe construction happens with an empty URL.

---

Nitpick comments:
In `@packages/genui/a2ui-playground/package.json`:
- Line 23: The dependency entry uses a redundant self-alias: update the
package.json dependency key "@types/react" to remove the "npm:" prefix and use a
normal semver version range (e.g. change from "npm:`@types/react`@^19.2.14" to a
plain "^19.2.14") so it matches the style of "@types/react-dom" and avoids
unnecessary aliasing.

In `@packages/genui/a2ui-playground/src/App.tsx`:
- Around line 26-34: The parseHash function can return componentName as an empty
string for hashes like "#/components/" or "#/components//foo"; update parseHash
so when parts[0] === 'components' it normalizes parts[1] to undefined if it's
missing or an empty string (e.g., set componentName to parts[1] || undefined or
explicitly check parts.length>1 && parts[1] !== ''), so ComponentsPage always
receives either a non-empty string or undefined.

In `@packages/genui/a2ui-playground/src/components/MobilePreview.tsx`:
- Line 12: The iframe in MobilePreview (the element rendering props.src) embeds
arbitrary URLs and should be hardened and deferred: update the iframe rendered
in MobilePreview to include a restrictive sandbox attribute (e.g.,
"allow-scripts allow-same-origin" or narrower depending on required features)
and add loading="lazy" to defer offscreen loading; keep the existing
title/className but ensure any required allow-* tokens match the preview
features you need (adjust sandbox tokens in the iframe created in MobilePreview
to avoid breaking functionality).

In `@packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx`:
- Around line 9-26: The buttons in ProtocolSwitch (the div with role='group' and
the two button elements using value and onChange) currently use only a visual
'protocolButton active' class to indicate selection; add proper toggle semantics
by setting aria-pressed on each button (e.g., aria-pressed={value === '0.8'} for
the v0.8 button and aria-pressed={value === '0.9'} for v0.9) or alternatively
convert the container to role='radiogroup' and each button to role='radio' with
aria-checked driven by value, ensuring the active state is exposed to assistive
tech while keeping existing onClick/onChange and className logic intact.

In `@packages/genui/a2ui-playground/src/demos.ts`:
- Around line 260-264: The Text demo entries with id 'cta-text' and 'btn-text'
are missing a variant while other Text nodes use variants like
'body'/'h2'/'caption'; update those two objects (ids 'cta-text' and 'btn-text')
to include variant: 'body' so their shape matches the other Text entries (or
alternatively normalize all Text entries by removing explicit variants—pick one
approach and apply uniformly).

In `@packages/genui/a2ui-playground/src/globals.d.ts`:
- Line 14: The current empty declaration "declare module '*.css' {}" only
supports side-effect imports; to prepare for future CSS Modules usage update the
declaration for the module pattern '*.css' to export a typed default (e.g., a
CSS module mapping or interface) so CSS imports have a typed default export;
modify the declaration in globals.d.ts (the "declare module '*.css'" block) to
export a default object type (like Record<string,string> or a named interface
such as CSSModule) so both side-effect and CSS Module imports are type-safe.

In `@packages/genui/a2ui-playground/src/mock/EventSource.ts`:
- Around line 19-27: The constructor-started emitSequence has a 10ms initial
delay causing a race where listeners added synchronously may miss the very first
event; change the constructor to schedule emitSequence via queueMicrotask(() =>
this.emitSequence()) instead of calling it directly, and inside emitSequence (or
a helper like hasListenersGuard) check whether any listeners (registered via
addEventListener) exist before emitting the first 'delta'—if none, wait (e.g.,
short loop with microtask/yield) until a listener is present or a small timeout
elapses; update references to emitSequence, constructor, and addEventListener
accordingly so the first event is reliably delivered to listeners.

In `@packages/genui/a2ui-playground/src/mock/messages/0.9/action_book.json`:
- Around line 1-14: Add a one-line comment at the top of action_book.json
stating that this fixture requires the surface "surface_sh_restaurants" to
already exist (created by card.json) before the update commands run; reference
that action_book.json is dispatched on USER_ACTION in EventSource.ts so
maintainers understand the load order and dependency.

In `@packages/genui/a2ui-playground/src/mock/mock-data-manager-08.ts`:
- Around line 49-51: getActionMocks08 currently always returns the full
actionMockMap; change its signature to match getActionMocks09 by accepting an
optional action parameter (e.g., action?: string) and, when action is provided,
return a new map containing only the entry for that action (or an empty map if
not found); otherwise return the full actionMockMap. Update the function name
getActionMocks08 and internal logic to perform the conditional filtering so
callers expecting the 0.9 behavior receive a single-entry map when passing an
action.

In `@packages/genui/a2ui-playground/src/mock/mock-data-manager-09.ts`:
- Around line 58-61: Change the function signature of getMockData09 to use an
optional parameter (mock?: string) instead of mock: string | undefined to match
getActionMocks09; update the declaration of getMockData09 and any internal
references to the parameter (resolveMockKey09(mock)) to use the new optional
parameter type while leaving the body returning mockDataMap[mockKey] ??
(cardMessages09 as unknown[]).

In `@packages/genui/a2ui-playground/src/pages/AIChatPage.tsx`:
- Around line 32-34: The useEffect that calls
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) runs on every
render because it has no dependency array; update the effect in AIChatPage by
adding a dependency array that includes the messages state (e.g., useEffect(...,
[messages])) so the auto-scroll only triggers when messages change; keep the
messagesEndRef reference inside the effect and ensure messages is the correct
variable name used in the component.
- Around line 25-27: The component AIChatPage currently takes _props: {
protocol: ProtocolVersion } but never uses the protocol prop; either wire it
into the mock response/preview logic (use the protocol value inside AIChatPage
to select/mock responses or pass it down to Preview/MockResponse components) or
remove the prop to avoid a misleading API; specifically update the AIChatPage
function signature (remove _props and its type) and any callers, or reference
props.protocol within the mock generation code (the places that build the
preview or mockResponse) so the ProtocolVersion is actually consumed.

In `@packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx`:
- Around line 57-109: ComponentGrid duplicates category filtering already done
in ComponentsPage; pass the precomputed grouping instead of re-filtering. Modify
ComponentsPage to pass groupedByCategory (the Map produced from CATEGORIES and
COMPONENT_CATALOG) as a prop to ComponentGrid (or export a shared helper that
builds the Map) and update ComponentGrid to accept that prop and iterate the Map
entries instead of calling COMPONENT_CATALOG.filter for each category; reference
the ComponentGrid and groupedByCategory symbols and remove the inline filtering
logic that uses COMPONENT_CATALOG.filter/CATEGORIES inside ComponentGrid.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 53-71: The catch in doRender currently uses String(e) when calling
setError, which yields verbose raw Error objects; update the catch to extract a
clean message (e instanceof Error ? e.message : String(e)) and pass that to
setError in place of String(e); if TypeScript complains, type the catch
parameter as unknown or narrow it with the instanceof check before using
.message; keep this change inside the doRender function around the JSON.parse
try/catch.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx`:
- Around line 68-74: In the catch block after JSON.parse in Dynamic.tsx, change
the error formatting to use the error message only; replace the current setError
call that uses String(e) with logic like e instanceof Error ? e.message :
String(e) so setError receives just the error message (refer to the catch(e)
block and the setError(...) call to locate where to update).

In `@packages/genui/a2ui-playground/src/render.tsx`:
- Around line 109-118: The effect currently sets lynxView.initData = initData ??
{} and calls lynxView.reload() unconditionally, causing a needless reload when
initData is null/empty; update the useEffect to early-return unless initData is
non-null and non-empty (e.g., check initData != null and
Object.keys(initData).length > 0), then assign lynxView.initData and call
lynxView.reload() only in that case — reference lynxViewRef.current,
lynxView.initData and lynxView.reload when making the change.

In `@pnpm-workspace.yaml`:
- Line 27: Move the "packages/genui/*" glob so it sits with the other
"packages/*" entries (i.e., alongside the existing package globs) and
reorder/group the "packages/*" lines alphabetically/consistently so all package
globs remain contiguous; update the list to keep the "packages/*" entries
together and sorted, ensuring "packages/genui/*" is placed among them rather
than after "website".
🪄 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: 8cebe6a3-157a-4a6b-9ee9-a0a18ee5eabd

📥 Commits

Reviewing files that changed from the base of the PR and between 5c39654 and cd4495c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (48)
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/App.tsx
  • packages/genui/a2ui-playground/src/componentCatalog.ts
  • packages/genui/a2ui-playground/src/components/Chip.tsx
  • packages/genui/a2ui-playground/src/components/MobilePreview.tsx
  • packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx
  • packages/genui/a2ui-playground/src/components/QrCode.tsx
  • packages/genui/a2ui-playground/src/components/UsageSection.tsx
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/entry.tsx
  • packages/genui/a2ui-playground/src/globals.d.ts
  • packages/genui/a2ui-playground/src/mock/EventSource.ts
  • packages/genui/a2ui-playground/src/mock/messages/0.8/action_book.json
  • packages/genui/a2ui-playground/src/mock/messages/0.8/card.json
  • packages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_abs_08.json
  • packages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_list_update_08.json
  • packages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_nested_08.json
  • packages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_numbers_booleans_08.json
  • packages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_relative_root_08.json
  • packages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_update_08.json
  • packages/genui/a2ui-playground/src/mock/messages/0.8/radio_group_08.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/action_book.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/card.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_abs.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_list.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_list_update.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_nested.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_numbers_booleans.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_relative_root.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_update.json
  • packages/genui/a2ui-playground/src/mock/messages/0.9/radio_group_09.json
  • packages/genui/a2ui-playground/src/mock/mock-data-manager-08.ts
  • packages/genui/a2ui-playground/src/mock/mock-data-manager-09.ts
  • packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
  • packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/pages/Dynamic.tsx
  • packages/genui/a2ui-playground/src/pages/Home.tsx
  • packages/genui/a2ui-playground/src/pages/Static.tsx
  • packages/genui/a2ui-playground/src/render.tsx
  • packages/genui/a2ui-playground/src/styles.css
  • packages/genui/a2ui-playground/src/utils/base64url.ts
  • packages/genui/a2ui-playground/src/utils/demoUrl.ts
  • packages/genui/a2ui-playground/src/utils/protocol.ts
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/a2ui-playground/tsconfig.json
  • pnpm-workspace.yaml

Comment thread packages/genui/a2ui-playground/src/mock/EventSource.ts
Comment thread packages/genui/a2ui-playground/src/mock/messages/0.8/card.json Outdated
Comment thread packages/genui/a2ui-playground/src/mock/mock-data-manager-08.ts Outdated
Comment thread packages/genui/a2ui-playground/src/pages/DemosPage.tsx Outdated
Comment thread packages/genui/a2ui-playground/src/pages/Home.tsx
Comment thread packages/genui/a2ui-playground/src/render.tsx
Comment thread packages/genui/a2ui-playground/src/styles.css
Comment thread packages/genui/a2ui-playground/src/styles.css
Comment thread packages/genui/a2ui-playground/src/utils/demoUrl.ts Outdated
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 17, 2026

Merging this PR will not alter performance

✅ 81 untouched benchmarks
⏩ 26 skipped benchmarks1


Comparing Sherry-hue:feat/a2ui-playground (aae5b4c) with main (d1743f2)

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.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 17, 2026

React External

#730 Bundle Size — 680.2KiB (0%).

1d913db(current) vs 647334c main#721(baseline)

Bundle metrics  no changes
                 Current
#730
     Baseline
#721
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
#730
     Baseline
#721
No change  Other 680.2KiB 680.2KiB

Bundle analysis reportBranch Sherry-hue:feat/a2ui-playgroundProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 17, 2026

React MTF Example

#745 Bundle Size — 196.47KiB (0%).

1d913db(current) vs 647334c main#736(baseline)

Bundle metrics  no changes
                 Current
#745
     Baseline
#736
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 173 173
No change  Duplicate Modules 66 66
No change  Duplicate Code 44.07% 44.07%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#745
     Baseline
#736
No change  IMG 111.23KiB 111.23KiB
No change  Other 85.24KiB 85.24KiB

Bundle analysis reportBranch Sherry-hue:feat/a2ui-playgroundProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 17, 2026

React Example

#7613 Bundle Size — 225.31KiB (0%).

1d913db(current) vs 647334c main#7604(baseline)

Bundle metrics  no changes
                 Current
#7613
     Baseline
#7604
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 179 179
No change  Duplicate Modules 69 69
No change  Duplicate Code 44.57% 44.57%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7613
     Baseline
#7604
No change  IMG 145.76KiB 145.76KiB
No change  Other 79.55KiB 79.55KiB

Bundle analysis reportBranch Sherry-hue:feat/a2ui-playgroundProject dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 17, 2026

Web Explorer

#9186 Bundle Size — 900.02KiB (0%).

1d913db(current) vs 647334c main#9177(baseline)

Bundle metrics  Change 2 changes
                 Current
#9186
     Baseline
#9177
No change  Initial JS 44.46KiB 44.46KiB
No change  Initial CSS 2.22KiB 2.22KiB
Change  Cache Invalidation 0% 16.16%
No change  Chunks 9 9
No change  Assets 11 11
Change  Modules 227(-0.87%) 229
No change  Duplicate Modules 11 11
Change  Duplicate Code 27.29%(+0.04%) 27.28%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#9186
     Baseline
#9177
No change  JS 495.88KiB 495.88KiB
No change  Other 401.92KiB 401.92KiB
No change  CSS 2.22KiB 2.22KiB

Bundle analysis reportBranch Sherry-hue:feat/a2ui-playgroundProject dashboard


Generated by RelativeCIDocumentationReport issue

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: 6

♻️ Duplicate comments (5)
packages/genui/a2ui-playground/src/styles.css (2)

913-920: ⚠️ Potential issue | 🟡 Minor

Stylelint: word-break: break-word is deprecated.

Replace with overflow-wrap: anywhere (or overflow-wrap: break-word).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 913 - 920, The
.chatMessage CSS rule uses the deprecated property word-break: break-word;
replace that declaration with overflow-wrap: anywhere (or overflow-wrap:
break-word) inside the .chatMessage rule so the element keeps long words/wrapped
content correctly; update the CSS selector .chatMessage to remove word-break and
add overflow-wrap: anywhere to resolve the Stylelint warning.

25-30: ⚠️ Potential issue | 🟡 Minor

Stylelint value-keyword-case still failing on unquoted font-family keywords.

The unquoted identifiers BlinkMacSystemFont, Roboto, Oxygen, Ubuntu, Cantarell, SFMono-Regular, Menlo, Consolas must be lowercased per the repo's stylelint config. Quoted family names like "Segoe UI", "Fira Sans", "Geist Mono" are unaffected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 25 - 30, The CSS
custom properties --geist-font and --geist-mono contain unquoted font-family
identifiers that violate stylelint value-keyword-case; update the unquoted
family names (BlinkMacSystemFont, Roboto, Oxygen, Ubuntu, Cantarell,
SFMono-Regular, Menlo, Consolas) to their lowercase equivalents
(blinkmacsystemfont, roboto, oxygen, ubuntu, cantarell, sfmono-regular, menlo,
consolas) so the values conform to the repo's stylelint rule while leaving
quoted families (e.g., "Segoe UI", "Fira Sans", "Geist Mono") unchanged.
packages/genui/a2ui-playground/src/pages/Home.tsx (1)

31-35: ⚠️ Potential issue | 🟡 Minor

Static chips still list components not in SUPPORTED_COMPONENTS.

Previously flagged and marked as addressed, but the literal ['Card', 'Form', 'List', 'Progress', 'Accordion'] is still here — Form, Progress, and Accordion are not in SUPPORTED_COMPONENTS (see packages/genui/a2ui-playground/src/demos.ts). The home hero still advertises components the playground doesn't support. Derive this list from SUPPORTED_COMPONENTS (or a curated subset of it) to prevent drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/Home.tsx` around lines 31 - 35, The
hard-coded chip list in Home.tsx (the <div className='chipRow'> mapping over
['Card','Form','List','Progress','Accordion']) is out of sync with
SUPPORTED_COMPONENTS in demos.ts; change the render to import and use
SUPPORTED_COMPONENTS (or a curated subset of it) and map over that array
(optionally filter or sort) so the displayed chips are derived from
SUPPORTED_COMPONENTS instead of a literal list.
packages/genui/a2ui-playground/src/pages/Dynamic.tsx (1)

30-33: ⚠️ Potential issue | 🟡 Minor

Refresh custom JSON when the protocol changes.

customJson is initialized once from the initial protocol; after switching protocols, entering Custom JSON can still show the old protocol payload unless the user manually clicks Fill Example.

Consider syncing it on protocol changes when the editor is still untouched, or when its current value matches the previous preset payload.

Also applies to: 103-111

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx` around lines 30 - 33,
customJson is only initialized once so switching protocol can leave the editor
showing the previous payload; update the component to watch protocol changes and
reset customJson when appropriate: detect the current editor value (via
customJson state) and if it is "untouched" (e.g. equals the previously formatted
preset payload) or if the user hasn't modified it, call
setCustomJson(formatJson(DYNAMIC_PRESETS[0]?.messagesByProtocol[protocol] ??
[])) on protocol change; implement this in a useEffect that references protocol,
customJson and uses formatJson and DYNAMIC_PRESETS to compute previous/next
payloads so the editor auto-syncs but preserves manual edits (also apply the
same logic to the similar block around the code referenced at lines 103-111).
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)

77-82: ⚠️ Potential issue | 🟠 Major

Keep the selected scenario and editor in sync on protocol changes.

This still re-renders ALL_SCENARIOS[0] instead of currentScenario, and it does not update customJson, so toggling protocol can show one JSON payload while previewing another.

Proposed fix
   useEffect(() => {
-    if (ALL_SCENARIOS[0]) {
-      const json = formatJson(ALL_SCENARIOS[0].messagesByProtocol[protocol]);
-      doRender(json, ALL_SCENARIOS[0]);
+    if (currentScenario) {
+      const json = formatJson(currentScenario.messagesByProtocol[protocol]);
+      setCustomJson(json);
+      doRender(json, currentScenario);
     }
-  }, [doRender, protocol]);
+  }, [currentScenario, doRender, protocol]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 77 - 82,
The effect currently always renders ALL_SCENARIOS[0] and doesn't update the
editor state when protocol changes; change the effect to use the selected
currentScenario (use currentScenario.messagesByProtocol[protocol]) instead of
ALL_SCENARIOS[0], call the same updater you use for the editor (e.g.,
setCustomJson or the existing doRender handler) with the new JSON so customJson
stays in sync, and include currentScenario in the dependency array along with
protocol and doRender so toggling protocol updates both the preview and editor
for the selected scenario.
🧹 Nitpick comments (5)
packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx (1)

95-100: Unknown component name silently falls back to the grid.

If the URL hash points at a component not in COMPONENT_CATALOG (e.g. typo, renamed component), selectedComp is undefined and the page quietly renders the full grid with no indication that the requested component wasn't found. Consider rendering a small "Component not found" state (with a link back to #/components) when componentName is set but unresolved, to avoid confusing deep-links.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx` around lines 95
- 100, The page silently falls back to the grid when a URL-targeted component is
missing: inside ComponentsPage, when componentName is non-empty but selectedComp
(computed from COMPONENT_CATALOG via useMemo) is undefined, add an explicit
"Component not found" UI state instead of rendering the full grid; detect
(componentName && !selectedComp) and render a small message/card with the
unresolved componentName and a link back to "#/components" (or a button) so
users know the deep-link failed and can return to the catalog.
packages/genui/a2ui-playground/src/pages/AIChatPage.tsx (1)

36-51: Uncleared setTimeout can setState after unmount.

If the user navigates away within 600ms of sending, the pending timer will call setMessages on an unmounted component (React logs a warning and the work is wasted). Track the timer in a ref and clear on unmount, or abort via an AbortController-style flag. Low impact given this is a playground, but worth tidying when a real LLM call replaces the mock.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/AIChatPage.tsx` around lines 36 -
51, The handleSend function schedules a setTimeout that may call setMessages
after the component unmounts; to fix, store the timeout id in a ref (e.g.,
responseTimerRef) when calling setTimeout in handleSend and clear it in a
cleanup effect (useEffect with return) so you call
clearTimeout(responseTimerRef.current) on unmount; update handleSend to assign
the timer id and ensure any previous timer is cleared before setting a new one
to avoid leaks when using MOCK_AI_RESPONSE.
packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx (1)

11-22: Single-option "switch" provides no interaction.

With ProtocolVersion = '0.9', this renders one always-active button whose click handler reselects the current value — effectively a static label. Consider either hiding this control until a second protocol version exists, or rendering it as a read-only badge (e.g., a Chip) to avoid confusing users with a non-functional toggle.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx` around
lines 11 - 22, The ProtocolSwitch component currently renders a single
interactive button (value prop '0.9' with onChange) that only re-selects the
same value; change ProtocolSwitch to detect when there is only one available
protocol (e.g., only '0.9') and either (a) render a non-interactive read-only
badge/chip element (replace the button with a span/div with class like
'protocolBadge' and no onClick) or (b) hide the entire control when multiple
options are not present; update the rendering branch that uses value and
onChange so you no longer show an active button that calls onChange('0.9') when
there are no alternative versions.
packages/genui/a2ui-playground/src/utils/protocol.ts (1)

4-12: Protocol type locked to '0.9' despite PR advertising v0.8/v0.9 toggle.

The PR description and UI screenshots reference both v0.8 and v0.9, but this module hard-codes ProtocolVersion = '0.9' and normalizeProtocol ignores its input and always returns '0.9'. This forces ProtocolSwitch to render only one button and makes the _value parameter dead code. If v0.8 support is intentionally deferred, consider either:

  • dropping the switch/normalizer entirely until a second version lands, or
  • widening ProtocolVersion to '0.8' | '0.9' now and actually honoring value in normalizeProtocol (falling back to DEFAULT_PROTOCOL on unknown input).

As-is, downstream usage: Record<ProtocolVersion, object> keying and the protocol switcher will need churn when v0.8 is added.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/utils/protocol.ts` around lines 4 - 12,
The ProtocolVersion type and normalizer are hard-coded to '0.9'; widen
ProtocolVersion to include both '0.8' | '0.9' and make normalizeProtocol(_value)
actually honor input by returning the value when it matches one of those allowed
versions, otherwise returning DEFAULT_PROTOCOL; keep DEFAULT_PROTOCOL as '0.9'
(or change if you prefer v0.8 default) and ensure normalizeProtocol accepts
string | null | undefined and performs a safe check against the ProtocolVersion
union before falling back.
packages/genui/a2ui-playground/lynx-src/App.tsx (1)

121-122: Prefer a concrete type over any for clientRef.

BaseClient is already imported at line 4; typing the ref as BaseClient | null removes the need for the biome-ignore and the downstream @typescript-eslint/no-unsafe-* disables (lines 146-194).

♻️ Proposed change
-  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
-  const clientRef = useRef<any>(null);
+  const clientRef = useRef<BaseClient | null>(null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/lynx-src/App.tsx` around lines 121 - 122,
clientRef is typed as any; change it to a concrete type by replacing
useRef<any>(null) with useRef<BaseClient | null>(null) (BaseClient is already
imported) and remove the biome-ignore lint comment; after this, remove the
downstream `@typescript-eslint/no-unsafe-`* disables that were added around the
code that uses clientRef (references in the region where clientRef.current is
accessed) so the code uses the proper typed BaseClient and TypeScript/ESLint
errors are resolved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-playground/lynx-src/App.tsx`:
- Around line 128-231: The effect's run() leaks fetches and swallows streaming
errors; modify loadMessages and loadActionMocks to accept an AbortSignal (from a
new AbortController created at the start of useEffect) and pass that signal into
their fetch calls, then call controller.abort() in the cleanup; update the run()
flow so the fire-and-forget streams in client.processUserAction (the IIFE) and
simulateStream are awaited or wrap their bodies in try/catch that calls
setError(String(err)) and cleans up state (setResource(null)/setLoading(false))
to surface errors; additionally implement a simple fetch timeout (e.g.,
setTimeout that aborts the controller) or reject after X ms to avoid indefinite
loading.
- Around line 189-194: The call to client.send currently uses an unnecessary
cast ('' as unknown) which bypasses TypeScript checks; remove the cast and pass
a properly typed A2UIClientEventMessage instead (either the plain string '' if
that matches the union or a structured object like { text: '' } typed as
A2UIClientEventMessage) when invoking client.send(message, messageId), and then
continue to store newResource via client.resources?.set?.(messageId,
newResource); ensure the argument you pass satisfies the A2UIClientEventMessage
type so no type assertions are needed.

In `@packages/genui/a2ui-playground/src/demos.ts`:
- Around line 58-87: The object literal in STATIC_DEMOS (e.g., the 'recs' demo)
includes a description property but the StaticDemo interface lacks it, causing
TypeScript excess-property errors; update the StaticDemo interface to include
description?: string (or description: string if required) and then ensure each
demo entry in STATIC_DEMOS (including recs and the other two demos) is populated
with a description, or alternatively remove the description fields from those
literals to match the current interface.

In `@packages/genui/a2ui-playground/src/pages/AIChatPage.tsx`:
- Around line 32-34: The effect that calls
messagesEndRef.current?.scrollIntoView is running on every render because
useEffect has no dependency array; update the useEffect that references
messagesEndRef to include a dependency array so it only runs when the chat
messages change (e.g., add [messages] as the dependency) rather than on every
render (which currently also triggers on inputValue updates).

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx`:
- Around line 140-160: The segmented control currently uses role='tablist' but
its children are plain buttons, so change the semantics to an ARIA toggle group:
remove role='tablist' and aria-label from the wrapping div, and add aria-pressed
attributes to the mode buttons so their pressed state reflects selection (e.g.,
on the Presets button rendered when hasPresets use aria-pressed={mode ===
'preset'}, and on the Custom button use aria-pressed={mode === 'custom'}); keep
handlers handleSwitchToPreset and handleSwitchToCustom and existing class
toggles unchanged.
- Around line 77-84: The custom-mode render path currently builds the render URL
without carrying over action mocks so dynamic preset actions break; update the
buildRenderUrl/RenderInit call that constructs url (the const url =
buildRenderUrl({...}, origin) using parsed/presetMessages) to also pass through
the preset's action mocks (presetActions or actionMocks) when present (same prop
name used by the preset branch), ensuring actionMocks is preserved into
RenderInit; locate uses of buildRenderUrl, RenderInit, parsed, presetMessages
and add the actionMocks/presetActions field to the payload.

---

Duplicate comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 77-82: The effect currently always renders ALL_SCENARIOS[0] and
doesn't update the editor state when protocol changes; change the effect to use
the selected currentScenario (use currentScenario.messagesByProtocol[protocol])
instead of ALL_SCENARIOS[0], call the same updater you use for the editor (e.g.,
setCustomJson or the existing doRender handler) with the new JSON so customJson
stays in sync, and include currentScenario in the dependency array along with
protocol and doRender so toggling protocol updates both the preview and editor
for the selected scenario.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx`:
- Around line 30-33: customJson is only initialized once so switching protocol
can leave the editor showing the previous payload; update the component to watch
protocol changes and reset customJson when appropriate: detect the current
editor value (via customJson state) and if it is "untouched" (e.g. equals the
previously formatted preset payload) or if the user hasn't modified it, call
setCustomJson(formatJson(DYNAMIC_PRESETS[0]?.messagesByProtocol[protocol] ??
[])) on protocol change; implement this in a useEffect that references protocol,
customJson and uses formatJson and DYNAMIC_PRESETS to compute previous/next
payloads so the editor auto-syncs but preserves manual edits (also apply the
same logic to the similar block around the code referenced at lines 103-111).

In `@packages/genui/a2ui-playground/src/pages/Home.tsx`:
- Around line 31-35: The hard-coded chip list in Home.tsx (the <div
className='chipRow'> mapping over ['Card','Form','List','Progress','Accordion'])
is out of sync with SUPPORTED_COMPONENTS in demos.ts; change the render to
import and use SUPPORTED_COMPONENTS (or a curated subset of it) and map over
that array (optionally filter or sort) so the displayed chips are derived from
SUPPORTED_COMPONENTS instead of a literal list.

In `@packages/genui/a2ui-playground/src/styles.css`:
- Around line 913-920: The .chatMessage CSS rule uses the deprecated property
word-break: break-word; replace that declaration with overflow-wrap: anywhere
(or overflow-wrap: break-word) inside the .chatMessage rule so the element keeps
long words/wrapped content correctly; update the CSS selector .chatMessage to
remove word-break and add overflow-wrap: anywhere to resolve the Stylelint
warning.
- Around line 25-30: The CSS custom properties --geist-font and --geist-mono
contain unquoted font-family identifiers that violate stylelint
value-keyword-case; update the unquoted family names (BlinkMacSystemFont,
Roboto, Oxygen, Ubuntu, Cantarell, SFMono-Regular, Menlo, Consolas) to their
lowercase equivalents (blinkmacsystemfont, roboto, oxygen, ubuntu, cantarell,
sfmono-regular, menlo, consolas) so the values conform to the repo's stylelint
rule while leaving quoted families (e.g., "Segoe UI", "Fira Sans", "Geist Mono")
unchanged.

---

Nitpick comments:
In `@packages/genui/a2ui-playground/lynx-src/App.tsx`:
- Around line 121-122: clientRef is typed as any; change it to a concrete type
by replacing useRef<any>(null) with useRef<BaseClient | null>(null) (BaseClient
is already imported) and remove the biome-ignore lint comment; after this,
remove the downstream `@typescript-eslint/no-unsafe-`* disables that were added
around the code that uses clientRef (references in the region where
clientRef.current is accessed) so the code uses the proper typed BaseClient and
TypeScript/ESLint errors are resolved.

In `@packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx`:
- Around line 11-22: The ProtocolSwitch component currently renders a single
interactive button (value prop '0.9' with onChange) that only re-selects the
same value; change ProtocolSwitch to detect when there is only one available
protocol (e.g., only '0.9') and either (a) render a non-interactive read-only
badge/chip element (replace the button with a span/div with class like
'protocolBadge' and no onClick) or (b) hide the entire control when multiple
options are not present; update the rendering branch that uses value and
onChange so you no longer show an active button that calls onChange('0.9') when
there are no alternative versions.

In `@packages/genui/a2ui-playground/src/pages/AIChatPage.tsx`:
- Around line 36-51: The handleSend function schedules a setTimeout that may
call setMessages after the component unmounts; to fix, store the timeout id in a
ref (e.g., responseTimerRef) when calling setTimeout in handleSend and clear it
in a cleanup effect (useEffect with return) so you call
clearTimeout(responseTimerRef.current) on unmount; update handleSend to assign
the timer id and ensure any previous timer is cleared before setting a new one
to avoid leaks when using MOCK_AI_RESPONSE.

In `@packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx`:
- Around line 95-100: The page silently falls back to the grid when a
URL-targeted component is missing: inside ComponentsPage, when componentName is
non-empty but selectedComp (computed from COMPONENT_CATALOG via useMemo) is
undefined, add an explicit "Component not found" UI state instead of rendering
the full grid; detect (componentName && !selectedComp) and render a small
message/card with the unresolved componentName and a link back to "#/components"
(or a button) so users know the deep-link failed and can return to the catalog.

In `@packages/genui/a2ui-playground/src/utils/protocol.ts`:
- Around line 4-12: The ProtocolVersion type and normalizer are hard-coded to
'0.9'; widen ProtocolVersion to include both '0.8' | '0.9' and make
normalizeProtocol(_value) actually honor input by returning the value when it
matches one of those allowed versions, otherwise returning DEFAULT_PROTOCOL;
keep DEFAULT_PROTOCOL as '0.9' (or change if you prefer v0.8 default) and ensure
normalizeProtocol accepts string | null | undefined and performs a safe check
against the ProtocolVersion union before falling back.
🪄 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: b2189378-8b5d-42ba-b321-821828ad81cc

📥 Commits

Reviewing files that changed from the base of the PR and between cd4495c and 93c6160.

📒 Files selected for processing (39)
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui-playground/lynx-src/index.css
  • packages/genui/a2ui-playground/lynx-src/index.tsx
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/lynx.config.ts
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/App.tsx
  • packages/genui/a2ui-playground/src/componentCatalog.ts
  • packages/genui/a2ui-playground/src/components/Chip.tsx
  • packages/genui/a2ui-playground/src/components/MobilePreview.tsx
  • packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx
  • packages/genui/a2ui-playground/src/components/QrCode.tsx
  • packages/genui/a2ui-playground/src/components/UsageSection.tsx
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/entry.tsx
  • packages/genui/a2ui-playground/src/globals.d.ts
  • packages/genui/a2ui-playground/src/mock/EventSource.ts
  • packages/genui/a2ui-playground/src/mock/messages/cast-grid.json
  • packages/genui/a2ui-playground/src/mock/messages/citywalk-list.json
  • packages/genui/a2ui-playground/src/mock/messages/recs.json
  • packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
  • packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/pages/Dynamic.tsx
  • packages/genui/a2ui-playground/src/pages/Home.tsx
  • packages/genui/a2ui-playground/src/pages/Static.tsx
  • packages/genui/a2ui-playground/src/render.tsx
  • packages/genui/a2ui-playground/src/styles.css
  • packages/genui/a2ui-playground/src/utils/base64url.ts
  • packages/genui/a2ui-playground/src/utils/demoUrl.ts
  • packages/genui/a2ui-playground/src/utils/protocol.ts
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/a2ui-playground/tsconfig.json
  • packages/genui/a2ui/src/catalog/Column/index.tsx
  • packages/genui/a2ui/src/catalog/Image/style.css
  • packages/genui/a2ui/src/catalog/List/index.tsx
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui/src/catalog/all.ts
✅ Files skipped from review due to trivial changes (16)
  • packages/genui/a2ui-playground/lynx-src/index.tsx
  • packages/genui/a2ui/src/catalog/Image/style.css
  • packages/genui/a2ui-playground/src/entry.tsx
  • packages/genui/a2ui-playground/src/components/Chip.tsx
  • packages/genui/a2ui-playground/src/components/MobilePreview.tsx
  • packages/genui/a2ui-playground/src/mock/messages/cast-grid.json
  • packages/genui/a2ui-playground/src/mock/messages/recs.json
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/a2ui-playground/src/globals.d.ts
  • packages/genui/a2ui-playground/src/mock/messages/citywalk-list.json
  • packages/genui/a2ui-playground/tsconfig.json
  • packages/genui/a2ui-playground/lynx.config.ts
  • packages/genui/a2ui-playground/src/utils/base64url.ts
  • packages/genui/a2ui-playground/src/components/QrCode.tsx
  • packages/genui/a2ui-playground/src/componentCatalog.ts
  • packages/genui/a2ui-playground/package.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/genui/a2ui-playground/src/utils/demoUrl.ts
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/App.tsx
  • packages/genui/a2ui-playground/src/pages/Static.tsx
  • packages/genui/a2ui-playground/src/render.tsx
  • packages/genui/a2ui-playground/src/mock/EventSource.ts

Comment thread packages/genui/a2ui-playground/lynx-src/App.tsx
Comment thread packages/genui/a2ui-playground/lynx-src/App.tsx
Comment thread packages/genui/a2ui-playground/src/demos.ts
Comment thread packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
Comment thread packages/genui/a2ui-playground/src/pages/Dynamic.tsx
Comment thread packages/genui/a2ui-playground/src/pages/Dynamic.tsx
@Sherry-hue Sherry-hue force-pushed the feat/a2ui-playground branch from 93c6160 to 9c2733c Compare April 23, 2026 09:58
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: 1

♻️ Duplicate comments (3)
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)

77-82: ⚠️ Potential issue | 🟠 Major

Effect hardcodes ALL_SCENARIOS[0] and does not resync the editor on protocol change.

When protocol changes, this effect always re-renders the first scenario regardless of the user's current selection, and customJson is never updated so the editor keeps showing the previous protocol's JSON while the preview renders the new one.

🛠️ Proposed fix
-  useEffect(() => {
-    if (ALL_SCENARIOS[0]) {
-      const json = formatJson(ALL_SCENARIOS[0].messagesByProtocol[protocol]);
-      doRender(json, ALL_SCENARIOS[0]);
-    }
-  }, [doRender, protocol]);
+  useEffect(() => {
+    if (currentScenario) {
+      const next = formatJson(currentScenario.messagesByProtocol[protocol]);
+      setCustomJson(next);
+      doRender(next, currentScenario);
+    }
+  }, [doRender, protocol, currentScenario]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 77 - 82,
The effect currently always uses ALL_SCENARIOS[0] and doesn't update the editor
JSON on protocol change; update the effect to operate on the currently selected
scenario (e.g., use your selectedScenario / activeScenario state instead of
ALL_SCENARIOS[0]), include that selected scenario and protocol in the dependency
array, and after formatting the new JSON call the setter that updates the editor
(e.g., setCustomJson) before calling doRender (keep using formatJson and
doRender but with the selected scenario and protocol). Ensure the effect
references the same unique symbols used in the file: formatJson, doRender,
ALL_SCENARIOS (only as a fallback), and the selected scenario state variable,
and add selected scenario and protocol to dependencies so the editor stays in
sync.
packages/genui/a2ui-playground/src/styles.css (2)

913-920: ⚠️ Potential issue | 🟡 Minor

Replace deprecated word-break: break-word.

Line 919 uses a deprecated keyword; switch to overflow-wrap to satisfy stylelint and modern behavior.

💅 Suggested fix
 .chatMessage {
   max-width: 80%;
   padding: 10px 14px;
   border-radius: var(--geist-radius-lg);
   font-size: 14px;
   line-height: 1.5;
-  word-break: break-word;
+  overflow-wrap: anywhere;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 913 - 920, The
.chatMessage rule uses the deprecated word-break: break-word; update the CSS by
removing that deprecated declaration and replacing it with the modern
overflow-wrap setting (use overflow-wrap: anywhere) in the .chatMessage selector
to satisfy stylelint and preserve the intended wrapping behavior.

25-30: ⚠️ Potential issue | 🟡 Minor

Unresolved stylelint keyword-case violations in font stacks.

System/generic font keywords are still mixed-case and will keep failing lint.

💅 Suggested fix
   --geist-font:
-    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
-    Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+    -apple-system, blinkmacsystemfont, "Segoe UI", roboto, oxygen, ubuntu,
+    cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
   --geist-mono:
-    "Geist Mono", "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas,
+    "Geist Mono", "SF Mono", sfmono-regular, ui-monospace, menlo, consolas,
     "Liberation Mono", monospace;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 25 - 30, Update
the font stacks in the CSS custom properties to use lowercase for all
font-family keywords so stylelint's keyword-case rule passes: in the
--geist-font and --geist-mono variables, convert any unquoted keyword-like
entries (e.g., BlinkMacSystemFont, SFMono-Regular, SF Mono, etc.) to lowercase
equivalents and ensure generic families are lowercase (sans-serif, monospace);
keep quoted proper names quoted but normalize any keyword-style names to
lowercase so the declarations in --geist-font and --geist-mono satisfy the
linter.
🧹 Nitpick comments (1)
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)

57-75: Parameter json shadows the imported json from @codemirror/lang-json.

The callback parameter name json shadows the module-level import used to build jsonExtensions. It's not a bug today (the import is only referenced at line 27), but it's confusing and fragile if future edits reach for json() inside this callback. Consider renaming to jsonString / source.

♻️ Proposed rename
-  const doRender = useCallback(
-    (json: string, scenario: Scenario | undefined) => {
+  const doRender = useCallback(
+    (jsonString: string, scenario: Scenario | undefined) => {
       setError('');
       let parsed: unknown;
       try {
-        parsed = JSON.parse(json);
+        parsed = JSON.parse(jsonString);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 57 - 75,
The parameter name json in the doRender useCallback shadows the module-level
import json from `@codemirror/lang-json`; rename the parameter (e.g., to
jsonString or source) and update all references inside doRender
(JSON.parse(json) -> JSON.parse(jsonString), any uses of json within the
function) so that the imported json remains accessible for building
jsonExtensions and to avoid future confusion; identifiers to touch: doRender,
the parameter named json, and the JSON.parse call and any other in-body
references to that parameter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 111-115: handleClear currently resets the editor to the string
'[]' which is inconsistent with scenario payloads (messagesByProtocol[protocol]
objects like { beginRender, surfaceUpdate }) and can surprise users; change
handleClear to set a more neutral initial payload such as '{}' or '' instead of
'[]' by updating the call to setCustomJson inside the handleClear callback
(referencing handleClear and setCustomJson), and ensure any callers or consumers
that rely on formatJson's nullish default behavior still handle the new empty
value correctly.

---

Duplicate comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 77-82: The effect currently always uses ALL_SCENARIOS[0] and
doesn't update the editor JSON on protocol change; update the effect to operate
on the currently selected scenario (e.g., use your selectedScenario /
activeScenario state instead of ALL_SCENARIOS[0]), include that selected
scenario and protocol in the dependency array, and after formatting the new JSON
call the setter that updates the editor (e.g., setCustomJson) before calling
doRender (keep using formatJson and doRender but with the selected scenario and
protocol). Ensure the effect references the same unique symbols used in the
file: formatJson, doRender, ALL_SCENARIOS (only as a fallback), and the selected
scenario state variable, and add selected scenario and protocol to dependencies
so the editor stays in sync.

In `@packages/genui/a2ui-playground/src/styles.css`:
- Around line 913-920: The .chatMessage rule uses the deprecated word-break:
break-word; update the CSS by removing that deprecated declaration and replacing
it with the modern overflow-wrap setting (use overflow-wrap: anywhere) in the
.chatMessage selector to satisfy stylelint and preserve the intended wrapping
behavior.
- Around line 25-30: Update the font stacks in the CSS custom properties to use
lowercase for all font-family keywords so stylelint's keyword-case rule passes:
in the --geist-font and --geist-mono variables, convert any unquoted
keyword-like entries (e.g., BlinkMacSystemFont, SFMono-Regular, SF Mono, etc.)
to lowercase equivalents and ensure generic families are lowercase (sans-serif,
monospace); keep quoted proper names quoted but normalize any keyword-style
names to lowercase so the declarations in --geist-font and --geist-mono satisfy
the linter.

---

Nitpick comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 57-75: The parameter name json in the doRender useCallback shadows
the module-level import json from `@codemirror/lang-json`; rename the parameter
(e.g., to jsonString or source) and update all references inside doRender
(JSON.parse(json) -> JSON.parse(jsonString), any uses of json within the
function) so that the imported json remains accessible for building
jsonExtensions and to avoid future confusion; identifiers to touch: doRender,
the parameter named json, and the JSON.parse call and any other in-body
references to that parameter.
🪄 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: 15066425-de53-47b6-acb0-f95fe9313c7f

📥 Commits

Reviewing files that changed from the base of the PR and between 93c6160 and 9c2733c.

📒 Files selected for processing (8)
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/mock/messages/cast-grid.json
  • packages/genui/a2ui-playground/src/mock/messages/fridge-search.json
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/styles.css
  • packages/genui/a2ui/src/catalog/Column/index.tsx
  • packages/genui/a2ui/src/catalog/Image/style.css
  • packages/genui/a2ui/src/catalog/Row/index.tsx
✅ Files skipped from review due to trivial changes (2)
  • packages/genui/a2ui/src/catalog/Image/style.css
  • packages/genui/a2ui-playground/src/mock/messages/cast-grid.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui/src/catalog/Column/index.tsx

Comment thread packages/genui/a2ui-playground/src/pages/DemosPage.tsx
@Sherry-hue Sherry-hue force-pushed the feat/a2ui-playground branch from ee452ea to 40e8cab Compare April 24, 2026 03:54
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

♻️ Duplicate comments (2)
packages/genui/a2ui-playground/src/styles.css (2)

25-30: ⚠️ Potential issue | 🟡 Minor

Stylelint still failing on value-keyword-case for unquoted system font identifiers.

BlinkMacSystemFont, Roboto, Oxygen, Ubuntu, Cantarell, SFMono-Regular, Menlo, Consolas are unquoted generic identifiers and must be lowercased per the repo's stylelint config. Quoted family names ("Segoe UI", "Fira Sans", "Geist Mono", "SF Mono", etc.) are unaffected.

💅 Proposed fix
   --geist-font:
-    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
-    Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+    -apple-system, blinkmacsystemfont, "Segoe UI", roboto, oxygen, ubuntu,
+    cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
   --geist-mono:
-    "Geist Mono", "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas,
+    "Geist Mono", "SF Mono", sfmono-regular, ui-monospace, menlo, consolas,
     "Liberation Mono", monospace;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 25 - 30,
Lowercase all unquoted system/generic font identifiers in the CSS variables to
satisfy stylelint value-keyword-case: update the --geist-font and --geist-mono
declarations by converting unquoted identifiers like BlinkMacSystemFont, Roboto,
Oxygen, Ubuntu, Cantarell, SFMono-Regular, Menlo, Consolas (and any other
unquoted names) to their lowercase equivalents; keep quoted family names (e.g.,
"Segoe UI", "Fira Sans", "Geist Mono", "SF Mono") unchanged so only the unquoted
identifiers are modified.

919-919: ⚠️ Potential issue | 🟡 Minor

Stylelint still failing: word-break: break-word is deprecated.

Replace with overflow-wrap: anywhere (preferred) or overflow-wrap: break-word.

💅 Proposed fix
 .chatMessage {
   max-width: 80%;
   padding: 10px 14px;
   border-radius: var(--geist-radius-lg);
   font-size: 14px;
   line-height: 1.5;
-  word-break: break-word;
+  overflow-wrap: anywhere;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` at line 919, Replace the
deprecated CSS declaration "word-break: break-word" with a supported
alternative: use "overflow-wrap: anywhere" (preferred) or "overflow-wrap:
break-word" wherever "word-break: break-word" appears in styles.css so stylelint
stops failing; search for the exact token "word-break: break-word" and update
each occurrence to "overflow-wrap: anywhere" (or "overflow-wrap: break-word" if
you need slightly different behavior).
🧹 Nitpick comments (2)
packages/genui/a2ui/src/catalog/Column/index.tsx (1)

38-52: Weight-based wrapper mirrors Row consistently.

Logic correctly guards on typeof weight === 'number' && weight > 0, so 0, negative, NaN, and missing values fall through to the unwrapped path. minHeight: 0 is the right counterpart to Row's minWidth: 0 for column flex children, and reading weight from child (rather than childWithContext) is equivalent since childWithContext is just a spread copy.

One minor consistency note: this duplicates the Row implementation almost verbatim. If a third axis-based container is added later, consider extracting a small renderWeightedChildren(axis: 'row' | 'column', ...) helper to centralize the cast and wrapper logic — not required for this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui/src/catalog/Column/index.tsx` around lines 38 - 52, The
column-weight wrapper logic duplicates the Row implementation; extract a small
helper (e.g., renderWeightedChild or renderWeightedChildren with signature like
(axis: 'row'|'column', child, childWithContext, childId, surface) ) to
centralize the weight cast/guard and wrapper creation: move the weight
extraction (const weight = (child as unknown as { weight?: number }).weight),
the typeof weight === 'number' && weight > 0 check, and the wrapper element
creation (className 'column-weighted-item' / corresponding row class, style
using flex and axis-specific minHeight/minWidth) into that helper, and call it
from Column.index.tsx (and the Row implementation) so NodeRenderer,
childWithContext, surface, and childId are passed through unchanged.
packages/genui/a2ui-playground/src/demos.ts (1)

119-122: as unknown[] cast on d.messages is misleading and not needed.

d.messages is typed as unknown, but each mock payload is actually an object (e.g. { messages: [...] } or { beginRender, surfaceUpdate }), not a unknown[]. Array.prototype.flatMap on a non-array return value coerces it to a single-element entry, so the cast doesn't really flatten anything — it just suppresses the type error. You can drop both the wrapper array and the casts and pass the raw payloads directly, since collectComponentNamesFromMessages already handles arbitrary unknown input.

♻️ Suggested simplification
-export const SUPPORTED_COMPONENTS = tagsFromMessages([
-  ...STATIC_DEMOS.flatMap((d) => d.messages as unknown[]),
-  ...DYNAMIC_PRESETS.flatMap((d) => d.messages as unknown[]),
-]);
+export const SUPPORTED_COMPONENTS = tagsFromMessages([
+  ...STATIC_DEMOS.map((d) => d.messages),
+  ...DYNAMIC_PRESETS.map((d) => d.messages),
+]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/demos.ts` around lines 119 - 122, Remove
the misleading casts and unnecessary flatMap wrapper: instead of using flatMap
with "d.messages as unknown[]" pass the raw message payloads directly because
tagsFromMessages/collectComponentNamesFromMessages already accept arbitrary
unknown input. Concretely, replace the flatMap calls over STATIC_DEMOS and
DYNAMIC_PRESETS with simple map/collection of d.messages (e.g. combine
STATIC_DEMOS.map(d => d.messages) and DYNAMIC_PRESETS.map(d => d.messages) into
the array passed to tagsFromMessages) and drop the "as unknown[]" casts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-playground/src/render.tsx`:
- Around line 44-46: The early-return in render.tsx currently guards on
protocol, messagesUrl, messages, and demoUrl and thus drops requests that only
supply action mocks; update the guard used before calling parseInitDataFromQuery
to also check for actionMocks and actionMocksUrl so URLs that only carry action
mock data are not treated as "empty" (i.e., include actionMocks and
actionMocksUrl in the condition that returns null), ensuring
parseInitDataFromQuery and lynxView.initData receive the mock data.
- Around line 16-22: InitData.protocol is typed as the literal '0.9' and
parseInitDataFromQuery coerces any non-'0.9' value to undefined, so when users
select the ProtocolSwitch to 0.8 the choice is dropped and never reaches
buildRenderUrl; fix by expanding InitData.protocol to accept both '0.8' | '0.9'
(or a union including other supported versions) and update
parseInitDataFromQuery to preserve/validate '0.8' as a valid value (e.g., accept
either '0.8' or '0.9' from the query rather than coercing to undefined),
ensuring buildRenderUrl receives and propagates the selected protocol;
alternatively, if 0.8 is not supported end-to-end, disable or hide
ProtocolSwitch's 0.8 option so users cannot select it.

---

Duplicate comments:
In `@packages/genui/a2ui-playground/src/styles.css`:
- Around line 25-30: Lowercase all unquoted system/generic font identifiers in
the CSS variables to satisfy stylelint value-keyword-case: update the
--geist-font and --geist-mono declarations by converting unquoted identifiers
like BlinkMacSystemFont, Roboto, Oxygen, Ubuntu, Cantarell, SFMono-Regular,
Menlo, Consolas (and any other unquoted names) to their lowercase equivalents;
keep quoted family names (e.g., "Segoe UI", "Fira Sans", "Geist Mono", "SF
Mono") unchanged so only the unquoted identifiers are modified.
- Line 919: Replace the deprecated CSS declaration "word-break: break-word" with
a supported alternative: use "overflow-wrap: anywhere" (preferred) or
"overflow-wrap: break-word" wherever "word-break: break-word" appears in
styles.css so stylelint stops failing; search for the exact token "word-break:
break-word" and update each occurrence to "overflow-wrap: anywhere" (or
"overflow-wrap: break-word" if you need slightly different behavior).

---

Nitpick comments:
In `@packages/genui/a2ui-playground/src/demos.ts`:
- Around line 119-122: Remove the misleading casts and unnecessary flatMap
wrapper: instead of using flatMap with "d.messages as unknown[]" pass the raw
message payloads directly because
tagsFromMessages/collectComponentNamesFromMessages already accept arbitrary
unknown input. Concretely, replace the flatMap calls over STATIC_DEMOS and
DYNAMIC_PRESETS with simple map/collection of d.messages (e.g. combine
STATIC_DEMOS.map(d => d.messages) and DYNAMIC_PRESETS.map(d => d.messages) into
the array passed to tagsFromMessages) and drop the "as unknown[]" casts.

In `@packages/genui/a2ui/src/catalog/Column/index.tsx`:
- Around line 38-52: The column-weight wrapper logic duplicates the Row
implementation; extract a small helper (e.g., renderWeightedChild or
renderWeightedChildren with signature like (axis: 'row'|'column', child,
childWithContext, childId, surface) ) to centralize the weight cast/guard and
wrapper creation: move the weight extraction (const weight = (child as unknown
as { weight?: number }).weight), the typeof weight === 'number' && weight > 0
check, and the wrapper element creation (className 'column-weighted-item' /
corresponding row class, style using flex and axis-specific minHeight/minWidth)
into that helper, and call it from Column.index.tsx (and the Row implementation)
so NodeRenderer, childWithContext, surface, and childId are passed through
unchanged.
🪄 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: 4fc57561-9d7f-468b-953c-909eaedb2e1f

📥 Commits

Reviewing files that changed from the base of the PR and between 9c2733c and 40e8cab.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (43)
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui-playground/lynx-src/index.css
  • packages/genui/a2ui-playground/lynx-src/index.tsx
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/lynx.config.ts
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/App.tsx
  • packages/genui/a2ui-playground/src/componentCatalog.ts
  • packages/genui/a2ui-playground/src/components/Chip.tsx
  • packages/genui/a2ui-playground/src/components/MobilePreview.tsx
  • packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx
  • packages/genui/a2ui-playground/src/components/QrCode.tsx
  • packages/genui/a2ui-playground/src/components/UsageSection.tsx
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/entry.tsx
  • packages/genui/a2ui-playground/src/globals.d.ts
  • packages/genui/a2ui-playground/src/mock/EventSource.ts
  • packages/genui/a2ui-playground/src/mock/messages/cast-grid.json
  • packages/genui/a2ui-playground/src/mock/messages/citywalk-list.json
  • packages/genui/a2ui-playground/src/mock/messages/fridge-search.json
  • packages/genui/a2ui-playground/src/mock/messages/recs.json
  • packages/genui/a2ui-playground/src/mock/messages/trip-planner.json
  • packages/genui/a2ui-playground/src/mock/messages/workout-plan.json
  • packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
  • packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/pages/Dynamic.tsx
  • packages/genui/a2ui-playground/src/pages/Home.tsx
  • packages/genui/a2ui-playground/src/pages/Static.tsx
  • packages/genui/a2ui-playground/src/render.tsx
  • packages/genui/a2ui-playground/src/styles.css
  • packages/genui/a2ui-playground/src/utils/base64url.ts
  • packages/genui/a2ui-playground/src/utils/demoUrl.ts
  • packages/genui/a2ui-playground/src/utils/protocol.ts
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/a2ui-playground/tsconfig.json
  • packages/genui/a2ui/src/catalog/Column/index.tsx
  • packages/genui/a2ui/src/catalog/Image/style.css
  • packages/genui/a2ui/src/catalog/List/index.tsx
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui/src/catalog/all.ts
  • pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (19)
  • packages/genui/a2ui-playground/src/utils/demoUrl.ts
  • pnpm-workspace.yaml
  • packages/genui/a2ui-playground/lynx-src/index.tsx
  • packages/genui/a2ui/src/catalog/Image/style.css
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/src/components/MobilePreview.tsx
  • packages/genui/a2ui-playground/src/utils/protocol.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui-playground/src/utils/base64url.ts
  • packages/genui/a2ui-playground/src/globals.d.ts
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/a2ui-playground/src/mock/messages/fridge-search.json
  • packages/genui/a2ui-playground/src/components/Chip.tsx
  • packages/genui/a2ui-playground/src/componentCatalog.ts
  • packages/genui/a2ui-playground/src/components/UsageSection.tsx
  • packages/genui/a2ui-playground/src/mock/messages/citywalk-list.json
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui-playground/src/mock/messages/recs.json
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/genui/a2ui-playground/src/entry.tsx
  • packages/genui/a2ui/src/catalog/List/index.tsx
  • packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx
  • packages/genui/a2ui-playground/src/pages/Home.tsx
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/tsconfig.json
  • packages/genui/a2ui-playground/src/components/QrCode.tsx
  • packages/genui/a2ui-playground/lynx.config.ts
  • packages/genui/a2ui-playground/src/mock/messages/cast-grid.json
  • packages/genui/a2ui-playground/src/pages/Dynamic.tsx
  • packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
  • packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx
  • packages/genui/a2ui-playground/src/mock/EventSource.ts

Comment thread packages/genui/a2ui-playground/src/render.tsx
Comment thread packages/genui/a2ui-playground/src/render.tsx
@Sherry-hue Sherry-hue force-pushed the feat/a2ui-playground branch from 40e8cab to a5c2b95 Compare April 24, 2026 04:35
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: 1

♻️ Duplicate comments (4)
packages/genui/a2ui-playground/src/pages/Dynamic.tsx (2)

83-90: ⚠️ Potential issue | 🟡 Minor

Custom-mode render URL still drops preset actionMocks.

The past-review fix for this looks to have been reverted — buildRenderUrl in the custom branch still omits actionMocks, so using "Fill Example" followed by "Start Rendering" in custom mode silently disables the preset's action triggers even though the messages reference them.

🛡️ Proposed fix
     const url = buildRenderUrl(
       {
         protocol,
         demoUrl,
         messages: parsed,
+        actionMocks: presetActions,
       },
       origin,
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx` around lines 83 - 90,
The custom-mode render flow is omitting preset actionMocks when calling
buildRenderUrl from Dynamic.tsx which causes "Fill Example" then "Start
Rendering" to lose preset action triggers; update the buildRenderUrl invocation
(or the custom-branch code path that assembles its payload) to include
actionMocks alongside protocol, demoUrl and messages (ensure the parsed
variable's actionMocks or the preset's actionMocks are passed through) so
buildRenderUrl receives and encodes actionMocks for custom-mode renders.

149-172: ⚠️ Potential issue | 🟡 Minor

Segmented control still uses invalid role='tablist' without tab children.

The wrapping div declares role='tablist' but the <button>s inside are not role='tab' and have no aria-selected, producing invalid ARIA semantics. Switch to a toggle-group model with aria-pressed.

🛡️ Proposed fix
           <div
             className='segmented'
-            role='tablist'
             aria-label='Dynamic demo mode'
           >
             {hasPresets
               ? (
                 <button
                   type='button'
                   className={mode === 'preset' ? 'segment active' : 'segment'}
+                  aria-pressed={mode === 'preset'}
                   onClick={handleSwitchToPreset}
                 >
                   Presets
                 </button>
               )
               : null}
             <button
               type='button'
               className={mode === 'custom' ? 'segment active' : 'segment'}
+              aria-pressed={mode === 'custom'}
               onClick={handleSwitchToCustom}
             >
               Custom JSON
             </button>
           </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx` around lines 149 - 172,
The segmented control uses role='tablist' but its buttons are not tabs; update
the markup in Dynamic.tsx to use a toggle-group pattern: remove role='tablist'
from the wrapping div and instead add aria-pressed to each button, set
aria-pressed={mode === 'preset'} for the Presets button and aria-pressed={mode
=== 'custom'} for the Custom JSON button, keep the existing class toggling and
onClick handlers (handleSwitchToPreset, handleSwitchToCustom) and ensure
hasPresets still controls rendering of the Presets button so the ARIA semantics
are valid.
packages/genui/a2ui-playground/src/styles.css (2)

913-920: ⚠️ Potential issue | 🟡 Minor

word-break: break-word is still flagged as deprecated by stylelint.

Replace with overflow-wrap: anywhere (or overflow-wrap: break-word).

💅 Proposed fix
 .chatMessage {
   max-width: 80%;
   padding: 10px 14px;
   border-radius: var(--geist-radius-lg);
   font-size: 14px;
   line-height: 1.5;
-  word-break: break-word;
+  overflow-wrap: anywhere;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 913 - 920, The
.chatMessage CSS rule uses the deprecated property word-break: break-word;
remove that declaration and replace it with a supported overflow-wrap value
(preferably overflow-wrap: anywhere; or overflow-wrap: break-word) inside the
same .chatMessage rule to preserve behavior; ensure only the deprecated line is
removed and the new overflow-wrap declaration is added alongside existing
properties (max-width, padding, border-radius, font-size, line-height).

25-30: ⚠️ Potential issue | 🟡 Minor

Stylelint value-keyword-case still failing — unquoted font family keywords must be lowercased.

Static analysis confirms these violations remain for BlinkMacSystemFont, Roboto, Oxygen, Ubuntu, Cantarell, SFMono-Regular, Menlo, and Consolas.

💅 Proposed fix
   --geist-font:
-    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
-    Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+    -apple-system, blinkmacsystemfont, "Segoe UI", roboto, oxygen, ubuntu,
+    cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
   --geist-mono:
-    "Geist Mono", "SF Mono", SFMono-Regular, ui-monospace, Menlo, Consolas,
+    "Geist Mono", "SF Mono", sfmono-regular, ui-monospace, menlo, consolas,
     "Liberation Mono", monospace;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 25 - 30, The CSS
custom properties --geist-font and --geist-mono contain unquoted font family
keywords with uppercase letters, causing stylelint value-keyword-case errors;
update the values by either lowercasing the unquoted keywords or quoting them
consistently — specifically fix BlinkMacSystemFont, Roboto, Oxygen, Ubuntu,
Cantarell in --geist-font and SFMono-Regular, Menlo, Consolas in --geist-mono
(e.g., make them lowercase or wrap each problematic name in quotes) so the
value-keyword-case rule is satisfied.
🧹 Nitpick comments (5)
packages/genui/a2ui/src/catalog/List/index.tsx (1)

57-73: Optional: hoist invariants out of the per-item loop.

template.componentId and the resulting surface.components.get(componentId) are constant for the lifetime of this render, so resolving them inside items.map does redundant Map lookups on every iteration and forces the loop to run even when the template component is missing. Hoisting them out also lets you bail out early with an empty content when the template component cannot be resolved.

♻️ Proposed refactor
   } else if (template) {
     const items = Array.isArray(listData) ? listData : [];
+    const templateChild = surface.components.get(template.componentId);
 
-    content = items.map((item, index) => {
-      const componentId = template.componentId;
-      const child = surface.components.get(componentId);
-      if (!child) return null;
-
-      const key = item && typeof item === 'object' && 'key' in item
-        ? String(item['key'])
-        : `${index}`;
-
-      const itemPath = `${fullPath}/${index}`;
-      const childWithContext = { ...child, dataContextPath: itemPath };
-
-      return {
-        key: key,
-        component: childWithContext,
-      };
-    });
+    if (templateChild) {
+      content = items.map((item, index) => {
+        const key = item && typeof item === 'object' && 'key' in item
+          ? String(item['key'])
+          : `${index}`;
+
+        return {
+          key,
+          component: { ...templateChild, dataContextPath: `${fullPath}/${index}` },
+        };
+      });
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui/src/catalog/List/index.tsx` around lines 57 - 73,
template.componentId and the lookup surface.components.get(componentId) are
invariant inside the items.map loop; hoist const componentId =
template.componentId and const child = surface.components.get(componentId) above
the mapping, check if child is falsy and set content = [] (or return early) to
avoid iterating, then inside the map use the hoisted child to build
childWithContext and the returned object (refer to items.map,
template.componentId, surface.components.get, childWithContext, content).
packages/genui/a2ui-playground/src/mock/messages/trip-planner.json (1)

84-85: Optional: staged-update pattern is only partially applied.

Day 1 nicely demonstrates the progressive/streaming UX by seeding placeholder text and then updating it (day1-weather "Preparing..." → "Sunny · 22°C · light breeze" at lines 85/104, and d1-stop1-desc "Fetching notes..." → final text at lines 135/147). The remaining stops (d1-stop2/3, all of Day 2) and day2-weather at line 250 are populated with their final text in one shot. Since this demo's stated purpose is "each stop filled progressively" (see STATIC_DEMOS.description in packages/genui/a2ui-playground/src/demos.ts), consider applying the same placeholder-then-finalize pattern to Day 2's weather and at least a couple of Day 2 stops for a more convincing streaming demo. No functional issue — purely a demo-quality refinement.

Also applies to: 104-104, 134-135, 147-148, 249-250

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/mock/messages/trip-planner.json` around
lines 84 - 85, The demo JSON only partially uses the staged-update pattern; to
fix, seed Day 2 and remaining Day 1 stops with placeholder entries (e.g., set
"day2-weather" to "Preparing..." and "d1-stop2", "d1-stop3", and a couple of
"d2-stopX-desc" entries to "Fetching notes...") and then insert/update their
final text later in the mock stream in the same style as "day1-weather" and
"d1-stop1-desc"; locate and modify the objects with ids "day1-weather",
"d1-stop1-desc", "d1-stop2", "d1-stop3", "day2-weather" and the Day 2 stop ids
(e.g., "d2-stop1-desc") so each has an initial placeholder component entry
followed by a later replacing entry with the real text to produce a
progressive/streaming UX.
packages/genui/a2ui-playground/src/pages/Dynamic.tsx (1)

53-101: Clicking "Start Rendering" twice with identical inputs won't refresh the preview.

setRenderUrl(url) is a no-op when the URL string is unchanged, so a user who edits the iframed preview state and wants to re-render the same JSON/preset sees nothing happen. For a playground whose point is iterative experimentation this is a small but noticeable UX papercut. Forcing a change (e.g. appending a monotonically increasing t= cache-buster) or unmounting/remounting the iframe would make "Start Rendering" always behave as expected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx` around lines 53 - 101,
handleStart currently calls setRenderUrl(url) with the same string so repeated
"Start Rendering" clicks are no-ops; modify handleStart to force a new URL each
time by adding a cache-buster (e.g., append a unique query param like
t=timestamp or an incrementing counter) when calling buildRenderUrl/setRenderUrl
or by toggling a short-lived state that causes the iframe to remount; update
references to buildRenderUrl, setRenderUrl, and the renderUrl state (and any
useEffect depending on renderUrl) so the iframe always receives a changed src
when Start is clicked even if the logical payload is identical.
packages/genui/a2ui-playground/lynx-src/App.tsx (2)

121-122: Prefer typing clientRef as BaseClient rather than using any.

BaseClient is already imported at line 4; using it as the ref generic would remove the need for the biome-ignore and the chain of @typescript-eslint/no-unsafe-* eslint-disables sprinkled below (lines 146, 151, 172, 183, 185, 188, 193, 197, 204).

♻️ Suggested refactor
-  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
-  const clientRef = useRef<any>(null);
+  const clientRef = useRef<BaseClient | null>(null);

You'll likely also want to narrow the processUserAction assignment via a local cast or a typed interface so the rest of the disables can be dropped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/lynx-src/App.tsx` around lines 121 - 122,
Replace the any-typed ref with a BaseClient-typed ref: change the clientRef
declaration to use useRef<BaseClient | null> (remove the biome-ignore comment)
and then narrow uses such as processUserAction by casting the stored value or
creating a small local interface/type so you can remove the
`@typescript-eslint/no-unsafe-`* disables around uses of clientRef (references:
clientRef, BaseClient, processUserAction).

141-144: Consider preserving pre-assigned messageId values if present in mocks.

Currently, both lines 141–144 and 163–167 unconditionally overwrite messageId on every message. While all examined mock payloads lack pre-assigned messageId values and the current single-turn grouping appears intentional, the type definition (A2uiMessage allows messageId?: string) suggests mocks could carry messages with existing ids.

If a future mock intentionally contains messages with pre-assigned messageIds for cross-turn references (e.g., a surfaceUpdate referencing a prior beginRender's id), this rewriting would silently re-key them and break routing.

Suggested fix:

Preserve existing messageIds
-      const messages = rawMessages.map((msg) => ({
-        ...msg,
-        messageId: messageId,
-      }));
+      const messages = rawMessages.map((msg) => ({
+        ...msg,
+        messageId: msg.messageId ?? messageId,
+      }));
...
-        const responseMessages = rawResponseMessages.map((msg) => ({
-          ...msg,
-          messageId: actionMessageId,
-        }));
+        const responseMessages = rawResponseMessages.map((msg) => ({
+          ...msg,
+          messageId: msg.messageId ?? actionMessageId,
+        }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/lynx-src/App.tsx` around lines 141 - 144, The
mapping that currently overwrites messageId on every message (seen in
rawMessages.map producing messages and the similar mapping later) should
preserve any pre-assigned ids: update the logic in the mappings that construct
messages (the rawMessages.map that assigns messageId and the second mapping at
lines referenced) to only set messageId when msg.messageId is undefined (e.g.,
use the existing msg.messageId if present, otherwise fall back to the generated
messageId), keeping types consistent with A2uiMessage so pre-assigned cross-turn
ids are not lost.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-playground/src/render.tsx`:
- Around line 94-105: The message handler currently accepts any postMessage
without validating origin; update the useEffect's handleMessage to check
e.origin against allowed origins before calling isInitLynxViewMessage and
setInitData (e.g., compare to window.location.origin and/or an explicit
allowlist). Specifically, in the handleMessage function used with
window.addEventListener('message', handleMessage) verify e.origin is in the
allowed set, then proceed to call isInitLynxViewMessage(e.data) and
setInitData(e.data.data); keep the removal in the cleanup unchanged.

---

Duplicate comments:
In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx`:
- Around line 83-90: The custom-mode render flow is omitting preset actionMocks
when calling buildRenderUrl from Dynamic.tsx which causes "Fill Example" then
"Start Rendering" to lose preset action triggers; update the buildRenderUrl
invocation (or the custom-branch code path that assembles its payload) to
include actionMocks alongside protocol, demoUrl and messages (ensure the parsed
variable's actionMocks or the preset's actionMocks are passed through) so
buildRenderUrl receives and encodes actionMocks for custom-mode renders.
- Around line 149-172: The segmented control uses role='tablist' but its buttons
are not tabs; update the markup in Dynamic.tsx to use a toggle-group pattern:
remove role='tablist' from the wrapping div and instead add aria-pressed to each
button, set aria-pressed={mode === 'preset'} for the Presets button and
aria-pressed={mode === 'custom'} for the Custom JSON button, keep the existing
class toggling and onClick handlers (handleSwitchToPreset, handleSwitchToCustom)
and ensure hasPresets still controls rendering of the Presets button so the ARIA
semantics are valid.

In `@packages/genui/a2ui-playground/src/styles.css`:
- Around line 913-920: The .chatMessage CSS rule uses the deprecated property
word-break: break-word; remove that declaration and replace it with a supported
overflow-wrap value (preferably overflow-wrap: anywhere; or overflow-wrap:
break-word) inside the same .chatMessage rule to preserve behavior; ensure only
the deprecated line is removed and the new overflow-wrap declaration is added
alongside existing properties (max-width, padding, border-radius, font-size,
line-height).
- Around line 25-30: The CSS custom properties --geist-font and --geist-mono
contain unquoted font family keywords with uppercase letters, causing stylelint
value-keyword-case errors; update the values by either lowercasing the unquoted
keywords or quoting them consistently — specifically fix BlinkMacSystemFont,
Roboto, Oxygen, Ubuntu, Cantarell in --geist-font and SFMono-Regular, Menlo,
Consolas in --geist-mono (e.g., make them lowercase or wrap each problematic
name in quotes) so the value-keyword-case rule is satisfied.

---

Nitpick comments:
In `@packages/genui/a2ui-playground/lynx-src/App.tsx`:
- Around line 121-122: Replace the any-typed ref with a BaseClient-typed ref:
change the clientRef declaration to use useRef<BaseClient | null> (remove the
biome-ignore comment) and then narrow uses such as processUserAction by casting
the stored value or creating a small local interface/type so you can remove the
`@typescript-eslint/no-unsafe-`* disables around uses of clientRef (references:
clientRef, BaseClient, processUserAction).
- Around line 141-144: The mapping that currently overwrites messageId on every
message (seen in rawMessages.map producing messages and the similar mapping
later) should preserve any pre-assigned ids: update the logic in the mappings
that construct messages (the rawMessages.map that assigns messageId and the
second mapping at lines referenced) to only set messageId when msg.messageId is
undefined (e.g., use the existing msg.messageId if present, otherwise fall back
to the generated messageId), keeping types consistent with A2uiMessage so
pre-assigned cross-turn ids are not lost.

In `@packages/genui/a2ui-playground/src/mock/messages/trip-planner.json`:
- Around line 84-85: The demo JSON only partially uses the staged-update
pattern; to fix, seed Day 2 and remaining Day 1 stops with placeholder entries
(e.g., set "day2-weather" to "Preparing..." and "d1-stop2", "d1-stop3", and a
couple of "d2-stopX-desc" entries to "Fetching notes...") and then insert/update
their final text later in the mock stream in the same style as "day1-weather"
and "d1-stop1-desc"; locate and modify the objects with ids "day1-weather",
"d1-stop1-desc", "d1-stop2", "d1-stop3", "day2-weather" and the Day 2 stop ids
(e.g., "d2-stop1-desc") so each has an initial placeholder component entry
followed by a later replacing entry with the real text to produce a
progressive/streaming UX.

In `@packages/genui/a2ui-playground/src/pages/Dynamic.tsx`:
- Around line 53-101: handleStart currently calls setRenderUrl(url) with the
same string so repeated "Start Rendering" clicks are no-ops; modify handleStart
to force a new URL each time by adding a cache-buster (e.g., append a unique
query param like t=timestamp or an incrementing counter) when calling
buildRenderUrl/setRenderUrl or by toggling a short-lived state that causes the
iframe to remount; update references to buildRenderUrl, setRenderUrl, and the
renderUrl state (and any useEffect depending on renderUrl) so the iframe always
receives a changed src when Start is clicked even if the logical payload is
identical.

In `@packages/genui/a2ui/src/catalog/List/index.tsx`:
- Around line 57-73: template.componentId and the lookup
surface.components.get(componentId) are invariant inside the items.map loop;
hoist const componentId = template.componentId and const child =
surface.components.get(componentId) above the mapping, check if child is falsy
and set content = [] (or return early) to avoid iterating, then inside the map
use the hoisted child to build childWithContext and the returned object (refer
to items.map, template.componentId, surface.components.get, childWithContext,
content).
🪄 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: 6b6e5efd-75fe-47cd-8dc7-7f4c60a29150

📥 Commits

Reviewing files that changed from the base of the PR and between 40e8cab and a5c2b95.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (43)
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui-playground/lynx-src/index.css
  • packages/genui/a2ui-playground/lynx-src/index.tsx
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/lynx.config.ts
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/App.tsx
  • packages/genui/a2ui-playground/src/componentCatalog.ts
  • packages/genui/a2ui-playground/src/components/Chip.tsx
  • packages/genui/a2ui-playground/src/components/MobilePreview.tsx
  • packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx
  • packages/genui/a2ui-playground/src/components/QrCode.tsx
  • packages/genui/a2ui-playground/src/components/UsageSection.tsx
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/entry.tsx
  • packages/genui/a2ui-playground/src/globals.d.ts
  • packages/genui/a2ui-playground/src/mock/EventSource.ts
  • packages/genui/a2ui-playground/src/mock/messages/cast-grid.json
  • packages/genui/a2ui-playground/src/mock/messages/citywalk-list.json
  • packages/genui/a2ui-playground/src/mock/messages/fridge-search.json
  • packages/genui/a2ui-playground/src/mock/messages/recs.json
  • packages/genui/a2ui-playground/src/mock/messages/trip-planner.json
  • packages/genui/a2ui-playground/src/mock/messages/workout-plan.json
  • packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
  • packages/genui/a2ui-playground/src/pages/ComponentsPage.tsx
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/pages/Dynamic.tsx
  • packages/genui/a2ui-playground/src/pages/Home.tsx
  • packages/genui/a2ui-playground/src/pages/Static.tsx
  • packages/genui/a2ui-playground/src/render.tsx
  • packages/genui/a2ui-playground/src/styles.css
  • packages/genui/a2ui-playground/src/utils/base64url.ts
  • packages/genui/a2ui-playground/src/utils/demoUrl.ts
  • packages/genui/a2ui-playground/src/utils/protocol.ts
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/a2ui-playground/tsconfig.json
  • packages/genui/a2ui/src/catalog/Column/index.tsx
  • packages/genui/a2ui/src/catalog/Image/style.css
  • packages/genui/a2ui/src/catalog/List/index.tsx
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui/src/catalog/all.ts
  • pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (21)
  • pnpm-workspace.yaml
  • packages/genui/a2ui-playground/src/components/MobilePreview.tsx
  • packages/genui/a2ui-playground/src/components/Chip.tsx
  • packages/genui/a2ui-playground/src/entry.tsx
  • packages/genui/a2ui-playground/lynx-src/index.tsx
  • packages/genui/a2ui/src/catalog/Image/style.css
  • packages/genui/a2ui-playground/tsconfig.json
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui-playground/src/components/ProtocolSwitch.tsx
  • packages/genui/a2ui-playground/src/utils/protocol.ts
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/a2ui-playground/src/mock/messages/cast-grid.json
  • packages/genui/a2ui-playground/src/mock/messages/workout-plan.json
  • packages/genui/a2ui-playground/src/mock/messages/recs.json
  • packages/genui/a2ui-playground/src/globals.d.ts
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui-playground/src/componentCatalog.ts
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/mock/messages/fridge-search.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/genui/a2ui-playground/src/utils/demoUrl.ts
  • packages/genui/a2ui-playground/src/pages/Home.tsx
  • packages/genui/a2ui-playground/src/components/UsageSection.tsx
  • packages/genui/a2ui-playground/src/components/QrCode.tsx
  • packages/genui/a2ui-playground/src/utils/base64url.ts
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui/src/catalog/Column/index.tsx
  • packages/genui/a2ui-playground/src/mock/EventSource.ts
  • packages/genui/a2ui-playground/src/mock/messages/citywalk-list.json

Comment thread packages/genui/a2ui-playground/src/render.tsx
@Sherry-hue Sherry-hue force-pushed the feat/a2ui-playground branch 3 times, most recently from aae5b4c to dc45aed Compare April 24, 2026 08:05
feat: add playground new style

feat: add lynx web project

feat: add json style

feat: use 0.9 mock data

feat: add data demo

feat: add citywalk demo

feat: add grid demo

feat: add weight prop && grid

fix: remove 0.9
@Sherry-hue Sherry-hue force-pushed the feat/a2ui-playground branch from dc45aed to 1d913db Compare April 24, 2026 08:40
@Sherry-hue Sherry-hue enabled auto-merge (squash) April 24, 2026 09:12
@Sherry-hue Sherry-hue merged commit 989f345 into lynx-family:main Apr 24, 2026
77 of 81 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 27, 2026
3 tasks
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