Skip to content

refactor(a2ui): React renderer + headless <A2UI> component#2571

Merged
PupilTong merged 1 commit into
lynx-family:mainfrom
PupilTong:claude/quirky-merkle-react
May 12, 2026
Merged

refactor(a2ui): React renderer + headless <A2UI> component#2571
PupilTong merged 1 commit into
lynx-family:mainfrom
PupilTong:claude/quirky-merkle-react

Conversation

@PupilTong
Copy link
Copy Markdown
Collaborator

@PupilTong PupilTong commented May 7, 2026

Summary

The React surface that consumes the message buffer + surface processor.

  • <A2UI> — all-in-one component owning a MessageProcessor per mount, subscribing to a developer-supplied MessageStore, processing tail messages each render, rendering the most recent surface.
  • <A2UIRenderer> / NodeRenderer — lower-level building blocks for consumers that want manual control over surface lifecycle.
  • <A2UIProvider> + useAction / useDataBinding / useCatalog — internal context + hooks the catalog components reach for.

Catalog migration:

  • Built-ins (Text, Button, Card, Column, List, Row, CheckBox, RadioGroup, Image, Divider) move from core/A2UIRender + global componentRegistry to the new react/A2UIRenderer. Side-effect re-exports (<Name>.ts, all.ts) and the global registry are dropped — every consumer composes via defineCatalog([...]).
  • src/index.ts is the new public surface; package.json exports trimmed (./core, ./chat, ./catalog/all removed).

Cleanup: delete src/core/, src/chat/, src/utils/ (replaced by the new layered design).

Playground:

  • lynx-src/App.tsx switches from BaseClient + A2UIRender to <A2UI messageStore={...} catalogs={...}> with the per-component manifest tuple form.
  • New examples/io-mock/ and examples/io-sse/ show how to push raw protocol messages into a MessageStore from a developer-owned IO module.
  • lynx-src/tsconfig.json enables resolveJsonModule for the catalog manifest imports.

Stacked on

PR-α: #2572 — feat(a2ui): add headless message buffer and surface processor.

The first commit on this branch is PR-α's content; once that lands and main is rebased into this branch, only the React-layer commit remains. Reviewers should focus on commit 2 (`refactor(a2ui): React renderer + headless component`) — that's the PR-β scope.

Test plan

  • `pnpm -F @lynx-js/a2ui-reactlynx test` — 46/46 pass
  • `pnpm -F a2ui-playground build:lynx` — clean build
  • Open the playground demos page; click "Render" on each scenario; the lynx surface renders as expected.

Summary by CodeRabbit

  • New Features

    • Introduced A2UI React component for rendering agent messages with flexible catalog composition
    • Added catalog builder API supporting dynamic component composition and tree-shaking
    • Added playground examples demonstrating IO integration patterns for agent communication
  • Documentation

    • Updated catalog README with component integration examples
    • Added playground examples documentation covering integration patterns

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 7, 2026

⚠️ No Changeset found

Latest commit: c60c802

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 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

This PR refactors A2UI from a resource-polling streaming model to a store-driven message-buffer architecture with React-based context rendering. It introduces MessageStore buffering, multi-listener MessageProcessor, immutable Resource snapshots, React hooks for binding and action dispatch, context-based provider/renderer, removes registry-based component wiring in favor of explicit tree-shakable exports, and adds agent example implementations (MockAgent, SseAgent) for the playground.

Changes

Store & Message Processing Foundation

Layer / File(s) Summary
Store Types & Contracts
src/store/types.ts
Introduces SurfaceId, ComponentInstance, Surface, ResourceInfo, ServerToClientMessage, UserActionPayload, A2UIClientEventMessage, and GenericComponentProps; replaces core/types.js as source of truth.
Resource State Machine
src/store/Resource.ts
New Resource<T> with immutable snapshots (pending | success | error), complete()/fail() transitions, subscribe() and deprecated onUpdate() callbacks; replaces utils/createResource with snapshot-based API.
Multi-Listener MessageProcessor
src/store/MessageProcessor.ts
Refactors onUpdate() and onEvent() to return unsubscribe closures; introduces emitUpdate() broadcast; synthesizes messageId fallback for rendering events.
Store Public API
src/store/index.ts
Exports MessageProcessor, createResource, Resource, SignalStore, message types, and payload normalizers.

React Hooks & Binding

Layer / File(s) Summary
useDataBinding & useResolvedProps
src/react/useDataBinding.ts
Implements React hooks using @preact/signals and useSyncExternalStore for signal subscription; replaces core-level hooks with reactive prop resolution.
useAction Hook
src/react/useAction.ts
New hook: resolves dynamic values, data bindings, and function calls; builds UserActionPayload and dispatches via processor.dispatch().
Context Hooks
src/react/useA2UIContext.ts, src/react/useCatalog.ts
Validates A2UI provider presence; exposes catalog map for component lookup.

React Provider & Renderer

Layer / File(s) Summary
A2UIProvider & Context
src/react/A2UIProvider.tsx
Exports A2UIContext, A2UIInternalContext, and provider component; memoizes catalog resolution via resolveCatalog().
A2UIRenderer & NodeRenderer
src/react/A2UIRenderer.tsx
Replaces core/A2UIRender: headless resource snapshot renderer with recursive buildNodeRecursive() and warnedTags tracking for missing components.
Main A2UI Component
src/react/A2UI.tsx
Core component: subscribes to MessageStore, maintains InternalSession, wires processor.onUpdate/onEvent, forwards actions, incrementally processes messages.
React Public API
src/react/index.ts
Exports A2UI, NodeRenderer, hooks, and type contracts.

Catalog Tree-Shaking & Exports

Layer / File(s) Summary
Remove Registry Wiring
src/catalog/Text.ts, Button.ts, Card.ts, CheckBox.ts, Column.ts, Divider.ts, Image.ts, List.ts, RadioGroup.ts, Row.ts, all.ts
Removes componentRegistry.register() calls and re-exports from individual catalog modules and barrel.
Explicit Component Exports
src/catalog/index.ts
Replaces export * from ./all.js with named exports for each component.
Catalog Documentation
src/catalog/README.md
Updates examples to show A2UI usage with catalogs prop; references schema extraction at dist/catalog/*/catalog.json.

Package API & Configuration

Layer / File(s) Summary
Core & Chat Cleanup
src/core/index.ts, src/chat/index.ts, src/utils/index.ts
Removes barrel re-exports; APIs now live in store and react layers.
Main Entrypoint
src/index.ts
New consolidated public API: re-exports React surface, store layer, catalog functions, and built-in components.
package.json Exports
package.json
Adds ./store, ./react, ./catalog/*; removes ./core, ./chat; switches catalog components to directory entrypoints; removes @lynx-js/lynx-ui-input peer dep.

Playground & Example Agents

Layer / File(s) Summary
Agent Examples
examples/io-mock/mockAgent.ts, examples/io-sse/sseAgent.ts
Reference implementations: createMockAgent() streams canned messages and action-triggered responses; createSseAgent() integrates EventSource with delta/complete batching.
Examples Documentation
examples/README.md
Describes scope: playground provides A2UI, MessageStore, and catalog API; developers own agent I/O, chunking, chat-shell behavior.
App.tsx Refactor
lynx-src/a2ui/App.tsx
Migrates from BaseClient+A2UIRender to MessageStore+createMockAgent+A2UI; updates catalog loading and init-data/message-mock handling.
Build Config
lynx-src/tsconfig.json
Enables resolveJsonModule; includes ../examples/**/*.ts(x) for type checking.

Test Coverage

Layer / File(s) Summary
Resource Lifecycle Tests
test/createResource.test.ts
Validates state transitions, snapshot identity, subscriptions, promise semantics, deprecated onUpdate.
MessageProcessor Tests
test/processor.test.ts
Tests multi-listener behavior, rendering lifecycle signals, surface deletion, data model updates.
Catalog Function Tests
test/catalog.test.ts
Tests defineCatalog, mergeCatalogs, resolveCatalog, serializeCatalog with name derivation and schema handling.

Deprecated & Removed APIs

Layer / File(s) Summary
Chat Module De-export
src/chat/Conversation.tsx, src/chat/useLynxClient.ts
Components retained in source but removed from chat/index.ts barrel.
Core Rendering Removal
src/core/A2UIRender.tsx, src/core/BaseClient.ts
Old resource-based rendering and SSE streaming removed; replaced by react/A2UIRenderer and agent patterns.
ComponentRegistry Removal
src/utils/ComponentRegistry.ts
Removes registry-based component registration class.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • HuJean
  • gaoachao
  • fzx2666-fz
  • Sherry-hue

🐰 From polling to messages, a rabbit's delight,
Resources now snapshot, transitions feel right,
Hooks bind the data, React takes the stage,
Agents handle streams—no more registry cage!
Tree-shaking at last, the catalog sings! 🌳

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

🧹 Nitpick comments (5)
packages/genui/a2ui/test/catalog.test.ts (1)

110-111: 💤 Low value

Hardcoded version '0.9' is brittle.

If serializeCatalog bumps its version, this test breaks without being related to the change under test. Consider importing the version constant from the implementation or at least leaving an inline comment documenting where this value comes from.

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

In `@packages/genui/a2ui/test/catalog.test.ts` around lines 110 - 111, Test uses a
brittle hardcoded version string ('0.9') in assertions; update the test to
reference the canonical version constant from the implementation (the value used
by serializeCatalog) instead of hardcoding, e.g., import the version constant
exported by the module that defines serializeCatalog or read it via the same
export that serializeCatalog uses, and replace the literal '0.9' in the
expect(out.version).toBe(...) assertion with that imported constant (or add an
inline comment pointing to the implementing symbol if importing isn’t possible).
packages/genui/a2ui/src/react/A2UIProvider.tsx (1)

34-36: ⚡ Quick win

Return type uses an inline import when ReactNode is already imported.

ReactNode is already imported on Line 5, so the inline import('@lynx-js/react').ReactNode is redundant.

🔧 Proposed fix
 export function A2UIProvider(
   props: ProviderProps,
-): import('@lynx-js/react').ReactNode {
+): ReactNode {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/genui/a2ui/src/react/A2UIProvider.tsx` around lines 34 - 36, The
return type of A2UIProvider uses an inline import
"import('@lynx-js/react').ReactNode" even though ReactNode is already imported;
update the A2UIProvider signature to use the existing ReactNode type (i.e.,
change the return type to ReactNode) and remove the inline import usage so the
function declaration simply reads A2UIProvider(props: ProviderProps): ReactNode
{, ensuring the file-level import for ReactNode (from the top, where it's
already imported) is used.
packages/genui/a2ui/test/messageStore.test.ts (1)

30-35: 💤 Low value

Test name is slightly misleading — no mutations occur in this test.

The test name says "between mutations" but only checks two consecutive getSnapshot() calls with no mutation in between. Consider renaming to 'getSnapshot returns the same reference on repeated calls' or adding a push(A) between the two calls to actually validate post-mutation stability (i.e., getSnapshot() after a mutation returns a new stable reference).

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

In `@packages/genui/a2ui/test/messageStore.test.ts` around lines 30 - 35, The test
name is misleading because no mutation happens; either rename the test to
reflect repeated calls (e.g., "'getSnapshot returns the same reference on
repeated calls'") or modify the test to perform a mutation between snapshots to
assert post-mutation stability — call createMessageStore(), take firstSnapshot =
store.getSnapshot(), perform a mutation via the store's mutation API (e.g.,
store.push(...) or store.addMessage(...)), then take secondSnapshot =
store.getSnapshot() and assert expected referential behavior; locate the test
using createMessageStore and getSnapshot to apply the change.
packages/genui/a2ui/test/payloadNormalizer.test.ts (1)

55-74: ⚡ Quick win

Document the mutation of activeSurfaceIds in the function's JSDoc comment.

The prepareMessagesForProcessing function mutates its activeSurfaceIds parameter by calling .delete() (line 180) and .add() (line 189) on it, but the JSDoc comment (lines 165–169) does not document this side-effect. Add a note like "@param activeSurfaceIds — mutated to track created/deleted surfaces" to clarify this out-parameter pattern, or alternatively, return the updated set from the function instead of mutating the input.

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

In `@packages/genui/a2ui/test/payloadNormalizer.test.ts` around lines 55 - 74, The
JSDoc for prepareMessagesForProcessing must document that the activeSurfaceIds
parameter is mutated (it calls .delete() and .add() to track created/deleted
surfaces); update the function comment to include a line such as "@param
activeSurfaceIds — mutated to track created/deleted surfaces" describing this
out-parameter side-effect, or alternatively change the function signature to
return the updated Set (and update callers) instead of mutating the input;
reference prepareMessagesForProcessing and the activeSurfaceIds parameter when
making the change.
packages/genui/a2ui/src/store/MessageProcessor.ts (1)

437-449: 🏗️ Heavy lift

Pass the surface in the deleteSurface event payload and delete first.

emitUpdate fires before this.surfaces.delete(surfaceId), so the A2UI.tsx listener works only because proc.getOrCreateSurface(surfaceId) still returns the live surface. Two consequences:

  • If the surface doesn't exist in this.surfaces (e.g., a deleteSurface arriving for an unknown/already-removed surfaceId), the listener's getOrCreateSurface synthesizes a phantom surface, which the subsequent this.surfaces.delete(surfaceId) then deletes. The deleteSurface event becomes a near-noop — the active resource never receives the deleteSurface snapshot because the phantom's resources map is empty.
  • Listeners must keep the implicit ordering contract in mind, which is fragile.

Including the original surface in the payload lets you delete first and removes the listener's dependency on map state:

♻️ Proposed refactor
       if ('deleteSurface' in message && message.deleteSurface) {
         const { surfaceId } = message.deleteSurface;
         const surface = this.surfaces.get(surfaceId);

+        this.surfaces.delete(surfaceId);
+
         this.emitUpdate({
           type: 'deleteSurface',
           surfaceId,
+          surface,
           targetId: surface?.rootComponentId ?? surfaceId,
           messageId: (message as { messageId?: string }).messageId,
         });
-
-        this.surfaces.delete(surfaceId);
       }

In A2UI.tsx, prefer the payload surface over proc.getOrCreateSurface(surfaceId) for the deleteSurface branch so the listener no longer creates phantom surfaces.

Based on learnings: "Update events (emitUpdate) should reflect the state after the mutation has been applied… Fix by applying the mutation (delete the surface) before emitting the update, or by passing a stable post-mutation snapshot to listeners."

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

In `@packages/genui/a2ui/src/store/MessageProcessor.ts` around lines 437 - 449,
The deleteSurface handler currently emits an update before removing the surface
from this.surfaces, causing listeners (e.g., A2UI.tsx using
proc.getOrCreateSurface) to see the pre-delete map state or synthesize phantom
surfaces; change the flow in the deleteSurface branch so you first retrieve and
delete the surface from this.surfaces (use this.surfaces.get(surfaceId) then
this.surfaces.delete(surfaceId)), and then call emitUpdate with a payload that
includes the original surface object (e.g., include surface in the emitted
payload along with surfaceId/targetId/messageId) so listeners receive a stable
post-delete snapshot and no longer rely on proc.getOrCreateSurface ordering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts`:
- Around line 138-170: The flush loop can lose batches when succeed() calls
cleanup() which clears queue; update logic so pending batches are drained before
resolve: either remove queue.length = 0 from cleanup() and let flush() drain
naturally, or change succeed() to set settled = true but not clear queue and
then modify flush() to continue processing until queue is empty (e.g., loop
condition uses (queue.length > 0 || !settled) or ensure it drains while
queue.length > 0 regardless of settled), making sure cleanup() still removes
listeners/timeouts after drain; reference succeed(), cleanup(), flush(), queue
and MESSAGE_PROCESS_DELAY when applying the change.

In `@packages/genui/a2ui-playground/package.json`:
- Line 9: The current "dev" script only runs "rsbuild dev" and no longer starts
the Lynx dev bundle; update the package.json "dev" script to run the Lynx dev
bundle concurrently with the standard dev server (i.e., start "rsbuild dev" and
whatever script exposes the Lynx dev bundle, commonly "rsbuild dev:lynx" or
"dev:lynx") using a concurrent runner (e.g., concurrently or npm-run-all) so the
Lynx dev server runs alongside the web dev server and avoids pre-building the
Lynx bundle or producing stale snapshots; adjust the script to invoke both
"rsbuild dev" and the Lynx dev command concurrently and ensure any added tool is
in devDependencies.

In `@packages/genui/a2ui/src/index.ts`:
- Around line 7-13: Add a brief inline comment above the export block explaining
that A2UIRenderer is intentionally omitted from public exports because it is an
internal implementation detail of A2UI (see react/index.ts), so contributors do
not re-export it; reference the public symbols A2UI, NodeRenderer, useAction,
useDataBinding, and useResolvedProps and mention A2UIRenderer by name to make
the intent explicit.

---

Nitpick comments:
In `@packages/genui/a2ui/src/react/A2UIProvider.tsx`:
- Around line 34-36: The return type of A2UIProvider uses an inline import
"import('@lynx-js/react').ReactNode" even though ReactNode is already imported;
update the A2UIProvider signature to use the existing ReactNode type (i.e.,
change the return type to ReactNode) and remove the inline import usage so the
function declaration simply reads A2UIProvider(props: ProviderProps): ReactNode
{, ensuring the file-level import for ReactNode (from the top, where it's
already imported) is used.

In `@packages/genui/a2ui/src/store/MessageProcessor.ts`:
- Around line 437-449: The deleteSurface handler currently emits an update
before removing the surface from this.surfaces, causing listeners (e.g.,
A2UI.tsx using proc.getOrCreateSurface) to see the pre-delete map state or
synthesize phantom surfaces; change the flow in the deleteSurface branch so you
first retrieve and delete the surface from this.surfaces (use
this.surfaces.get(surfaceId) then this.surfaces.delete(surfaceId)), and then
call emitUpdate with a payload that includes the original surface object (e.g.,
include surface in the emitted payload along with surfaceId/targetId/messageId)
so listeners receive a stable post-delete snapshot and no longer rely on
proc.getOrCreateSurface ordering.

In `@packages/genui/a2ui/test/catalog.test.ts`:
- Around line 110-111: Test uses a brittle hardcoded version string ('0.9') in
assertions; update the test to reference the canonical version constant from the
implementation (the value used by serializeCatalog) instead of hardcoding, e.g.,
import the version constant exported by the module that defines serializeCatalog
or read it via the same export that serializeCatalog uses, and replace the
literal '0.9' in the expect(out.version).toBe(...) assertion with that imported
constant (or add an inline comment pointing to the implementing symbol if
importing isn’t possible).

In `@packages/genui/a2ui/test/messageStore.test.ts`:
- Around line 30-35: The test name is misleading because no mutation happens;
either rename the test to reflect repeated calls (e.g., "'getSnapshot returns
the same reference on repeated calls'") or modify the test to perform a mutation
between snapshots to assert post-mutation stability — call createMessageStore(),
take firstSnapshot = store.getSnapshot(), perform a mutation via the store's
mutation API (e.g., store.push(...) or store.addMessage(...)), then take
secondSnapshot = store.getSnapshot() and assert expected referential behavior;
locate the test using createMessageStore and getSnapshot to apply the change.

In `@packages/genui/a2ui/test/payloadNormalizer.test.ts`:
- Around line 55-74: The JSDoc for prepareMessagesForProcessing must document
that the activeSurfaceIds parameter is mutated (it calls .delete() and .add() to
track created/deleted surfaces); update the function comment to include a line
such as "@param activeSurfaceIds — mutated to track created/deleted surfaces"
describing this out-parameter side-effect, or alternatively change the function
signature to return the updated Set (and update callers) instead of mutating the
input; reference prepareMessagesForProcessing and the activeSurfaceIds parameter
when making the change.
🪄 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: 7524e074-1b6f-40a1-9445-fe6af09be659

📥 Commits

Reviewing files that changed from the base of the PR and between 8417e68 and 6d0a83d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (58)
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/rstest.config.ts
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/catalog/README.md
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/core/BaseClient.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/A2UI.tsx
  • packages/genui/a2ui/src/react/A2UIProvider.tsx
  • packages/genui/a2ui/src/react/A2UIRenderer.tsx
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui/src/react/useDataBinding.ts
  • packages/genui/a2ui/src/store/MessageProcessor.ts
  • packages/genui/a2ui/src/store/MessageStore.ts
  • packages/genui/a2ui/src/store/Resource.ts
  • packages/genui/a2ui/src/store/SignalStore.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/payloadNormalizer.ts
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/createResource.test.ts
  • packages/genui/a2ui/test/messageStore.test.ts
  • packages/genui/a2ui/test/payloadNormalizer.test.ts
  • packages/genui/a2ui/test/processor.test.ts
  • packages/genui/a2ui/tsconfig.build.json
  • packages/genui/a2ui/tsconfig.json
  • packages/genui/tsconfig.json
💤 Files with no reviewable changes (24)
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/core/BaseClient.ts

Comment thread packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts Outdated
Comment thread packages/genui/a2ui-playground/package.json Outdated
Comment thread packages/genui/a2ui/src/index.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented May 7, 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!

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 7, 2026

Merging this PR will improve performance by 19.21%

⚡ 2 improved benchmarks
✅ 79 untouched benchmarks
⏩ 26 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
008-many-use-state-destroyBackground 9.5 ms 8 ms +19.21%
transform 1000 view elements 43.1 ms 40 ms +7.71%

Comparing PupilTong:claude/quirky-merkle-react (c60c802) with main (33b124f)

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 May 7, 2026

Web Explorer

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

c60c802(current) vs 33b124f main#9602(baseline)

Bundle metrics  Change 1 change
                 Current
#9619
     Baseline
#9602
No change  Initial JS 44.46KiB 44.46KiB
No change  Initial CSS 2.22KiB 2.22KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 9 9
No change  Assets 11 11
Change  Modules 228(-0.44%) 229
No change  Duplicate Modules 11 11
No change  Duplicate Code 27.28% 27.28%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#9619
     Baseline
#9602
No change  JS 495.91KiB 495.91KiB
No change  Other 401.92KiB 401.92KiB
No change  CSS 2.22KiB 2.22KiB

Bundle analysis reportBranch PupilTong:claude/quirky-merkle-r...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 7, 2026

React Example with Element Template

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

c60c802(current) vs 33b124f main#295(baseline)

Bundle metrics  no changes
                 Current
#312
     Baseline
#295
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 81 81
No change  Duplicate Modules 23 23
No change  Duplicate Code 40.29% 40.29%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#312
     Baseline
#295
No change  IMG 145.76KiB 145.76KiB
No change  Other 52.03KiB 52.03KiB

Bundle analysis reportBranch PupilTong:claude/quirky-merkle-r...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 7, 2026

React Example

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

c60c802(current) vs 33b124f main#8029(baseline)

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

Bundle analysis reportBranch PupilTong:claude/quirky-merkle-r...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 7, 2026

React External

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

c60c802(current) vs 33b124f main#1143(baseline)

Bundle metrics  no changes
                 Current
#1160
     Baseline
#1143
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
#1160
     Baseline
#1143
No change  Other 690.27KiB 690.27KiB

Bundle analysis reportBranch PupilTong:claude/quirky-merkle-r...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 7, 2026

React MTF Example

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

c60c802(current) vs 33b124f main#1160(baseline)

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

Bundle analysis reportBranch PupilTong:claude/quirky-merkle-r...Project dashboard


Generated by RelativeCIDocumentationReport issue

PupilTong added a commit that referenced this pull request May 8, 2026
## Summary

Foundational message-buffer + payload-normalization layer for the
headless A2UI renderer. Pure data logic, no React surface, no breaking
changes.

- `MessageStore` — append-only buffer of raw protocol messages with a
`useSyncExternalStore`-friendly `subscribe` / `getSnapshot` API.
- `payloadNormalizer` — turns arbitrary developer payloads (string,
structured object, `{ kind, data }` envelope, nested arrays) into a flat
`ServerToClientMessage[]`. Includes
`createFallbackMessagesFromPlainText`, `createTextCardMessages`, and
`prepareMessagesForProcessing` helpers used by IO transports.

Adds a per-package `rstest.config.ts` plus split `tsconfig.json` (lint +
tests) / `tsconfig.build.json` (emit) so tests run via `pnpm -F
@lynx-js/a2ui-reactlynx test` without polluting the emit `rootDir`.

This PR is **purely additive** — the existing `core/`, `chat/`, and
`utils/` paths (`BaseClient`, `A2UIRender`, the global
`componentRegistry`, the legacy `createResource` / `SignalStore`) keep
working untouched. The bigger types-divergent pieces
(`MessageProcessor`, the new `Resource` with `subscribe` / `getSnapshot`
/ explicit status, the new `SignalStore`) ship together with the React
renderer in
[#2571](#2571), since they
introduce a new `Surface` shape that the catalog components must migrate
to in the same PR.

## Test plan

- [ ] \`pnpm -F @lynx-js/a2ui-reactlynx test\` — 16/16 pass
(`MessageStore`, `payloadNormalizer`)
- [ ] \`pnpm -F @lynx-js/a2ui-reactlynx build\` — extractor still emits
the 10 catalog manifests
- [ ] Existing playground (which uses `core/`/`chat/` paths) still runs

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a message store and payload-normalization utilities for handling
and preparing runtime messages.

* **Tests**
* Added tests covering message store behavior, payload normalization,
message processing, and edge cases.

* **Chores**
* Integrated a test runner and dev-dependency, and updated TypeScript
build/test configurations and project references.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@PupilTong PupilTong force-pushed the claude/quirky-merkle-react branch from 6d0a83d to 2c893d4 Compare May 8, 2026 03:29
@PupilTong PupilTong self-assigned this May 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@packages/genui/a2ui/src/store/MessageProcessor.ts`:
- Around line 441-446: The deleteSurface emit populates messageId optionally and
can emit undefined; update the emit in MessageProcessor (the deleteSurface path
that calls this.emitUpdate) to normalize the messageId the same way
beginRendering does by falling back to a stable id like `surface:${surfaceId}`
when (message as { messageId?: string }).messageId is missing or falsy; locate
the deleteSurface emission (variables: surfaceId, surface, message) and replace
the raw messageId with a normalized value before calling this.emitUpdate so
consumers always receive a non-undefined messageId.
- Around line 44-51: dispatch currently passes the same resolve function to
every subscriber in this.eventListeners, causing a race where the first caller
resolves the shared Promise; fix by switching to a deterministic pattern: either
(A) enforce a single active listener by only invoking the first callback in
this.eventListeners (if onEvent is meant to be single-subscriber), or (B)
implement fan-out with aggregation by mapping each listener callback to its own
Promise (or require listeners to return a Promise) and then return
Promise.all(...) of those per-listener Promises so dispatch resolves with an
array of results; update the onEvent contract/documentation accordingly
(referencing dispatch, this.eventListeners, and onEvent) and ensure tests
reflect the chosen behavior.
🪄 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: 7d27f021-660e-4bb3-b3f3-989e234eea35

📥 Commits

Reviewing files that changed from the base of the PR and between 6d0a83d and 2c893d4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (50)
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/catalog/README.md
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/core/BaseClient.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/A2UI.tsx
  • packages/genui/a2ui/src/react/A2UIProvider.tsx
  • packages/genui/a2ui/src/react/A2UIRenderer.tsx
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui/src/react/useDataBinding.ts
  • packages/genui/a2ui/src/store/MessageProcessor.ts
  • packages/genui/a2ui/src/store/Resource.ts
  • packages/genui/a2ui/src/store/SignalStore.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/createResource.test.ts
  • packages/genui/a2ui/test/processor.test.ts
💤 Files with no reviewable changes (24)
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/core/BaseClient.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
✅ Files skipped from review due to trivial changes (7)
  • packages/genui/a2ui-playground/package.json
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/A2UI.tsx
  • packages/genui/a2ui/src/catalog/README.md
  • packages/genui/a2ui/src/react/useDataBinding.ts
🚧 Files skipped from review as they are similar to previous changes (16)
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui/src/react/A2UIProvider.tsx
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui/test/processor.test.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/test/createResource.test.ts
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/A2UIRenderer.tsx
  • packages/genui/a2ui/src/store/Resource.ts
  • packages/genui/a2ui-playground/lynx-src/App.tsx

Comment thread packages/genui/a2ui/src/store/MessageProcessor.ts
Comment thread packages/genui/a2ui/src/store/MessageProcessor.ts
@PupilTong PupilTong force-pushed the claude/quirky-merkle-react branch from 2c893d4 to 0940278 Compare May 8, 2026 05:04
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: 5

🧹 Nitpick comments (1)
packages/genui/a2ui/src/react/useDataBinding.ts (1)

60-66: ⚡ Quick win

Centralize and normalize binding path resolution.

The same path-joining logic is copied in three places, and it only prefixes segments. Relative bindings like ../foo or ./foo stay unnormalized, so reads and writes can drift if another layer expects canonical store keys.

♻️ Suggested direction
+function resolveStorePath(path: string | undefined, dataContextPath?: string) {
+  if (!path) return undefined;
+  const raw = path.startsWith('/')
+    ? path
+    : `${dataContextPath ?? ''}/${path}`;
+
+  const normalized: string[] = [];
+  for (const segment of raw.split('/')) {
+    if (!segment || segment === '.') continue;
+    if (segment === '..') {
+      normalized.pop();
+      continue;
+    }
+    normalized.push(segment);
+  }
+  return `/${normalized.join('/')}`;
+}

Then reuse that helper for all three read/write call sites.

Also applies to: 125-127, 203-205

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

In `@packages/genui/a2ui/src/react/useDataBinding.ts` around lines 60 - 66, The
path-joining logic is duplicated and doesn't normalize relative segments like
"../" or "./"; create a single helper (e.g., normalizeBindingPath or
resolveBindingPath) that accepts (path: string, dataContextPath?: string) and
returns a canonical absolute-like path by: 1) prefixing dataContextPath when
path doesn't start with '/', 2) collapsing "." and ".." segments and duplicate
slashes, and 3) trimming any trailing slash except for root; then replace the
three inlined blocks (the current join logic found around the path handling in
useDataBinding.ts) with calls to this helper for both reads and writes so all
callers use the same normalized key space.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/genui/a2ui/src/catalog/README.md`:
- Around line 11-13: Update the README to warn that relying on bare component
names via `displayName ?? component.name` is unsafe in production because
toplevel name mangling during minification can remove `component.name`; advise
authors to set an explicit `displayName` on components or use the
manifest/manifest form when calling `defineCatalog` so protocol names remain
stable, and mention that `defineCatalog` will throw if both `displayName` and
`name` are absent (but only manifests in production builds), so include this
warning near the existing examples that reference `displayName ??
component.name`.

In `@packages/genui/a2ui/src/react/A2UIRenderer.tsx`:
- Around line 106-111: The current use of the nullish coalescing operator treats
an explicit null from renderFallback or renderError as “no override”, preventing
consumers from intentionally returning null; change both branches to call
renderFallback?.() and renderError?.(error), capture the result, and return the
result if it is not strictly undefined (result !== undefined), otherwise fall
back to the built-in UI (DefaultLoading or the default error text); update the
logic around renderFallback, renderError, DefaultLoading, and the error branch
in A2UIRenderer to use this undefined-check pattern.

In `@packages/genui/a2ui/src/react/useDataBinding.ts`:
- Around line 87-89: The currentValue ternary uses (signalValue ?? initialValue)
when path is truthy, which skips the caller-provided fallbackValue; change the
path branch in useDataBinding (the currentValue computation) to prefer
signalValue, then fallbackValue, then initialValue (e.g., signalValue ??
fallbackValue ?? initialValue) so unresolved bound paths use the provided
fallback.

In `@packages/genui/a2ui/src/store/MessageProcessor.ts`:
- Around line 56-79: dispatch() currently waits for each listener to call a
provided resolve callback, so one non-calling listener can block forever; change
the listener contract in MessageProcessor so listeners return a value or Promise
and make dispatch treat each cb(...) as Promise.resolve(returnedValue) and use
Promise.race([... , timeoutPromise]) per listener to enforce a per-listener
timeout; in practice update the loop that invokes eventListeners (in
MessageProcessor.dispatch / the tryResolve/cb invocation area) to call const p =
Promise.resolve(cb({ message })) then p.then(value => { if (!hasResponse) {
hasResponse = true; firstResponse = value; } }).finally(() => { settled += 1; if
(settled >= total) resolve(hasResponse ? firstResponse : []); }) and remove the
old resolve callback param (or keep but ignore for compatibility) so one stalled
listener no longer blocks dispatch.
- Around line 73-76: The dispatch() logic sets hasResponse and firstResponse on
any resolved value, so an empty payload (e.g., [] or '') can mark the "first
non-empty response" incorrectly; change the assignment so
hasResponse/firstResponse are only set when the resolved value is actually
non-empty—implement a simple truthy/non-empty check (e.g., value !==
null/undefined and for arrays length>0, for strings length>0, for objects
Object.keys(value).length>0) before setting hasResponse and firstResponse in the
dispatch handler (refer to dispatch(), hasResponse, firstResponse).

---

Nitpick comments:
In `@packages/genui/a2ui/src/react/useDataBinding.ts`:
- Around line 60-66: The path-joining logic is duplicated and doesn't normalize
relative segments like "../" or "./"; create a single helper (e.g.,
normalizeBindingPath or resolveBindingPath) that accepts (path: string,
dataContextPath?: string) and returns a canonical absolute-like path by: 1)
prefixing dataContextPath when path doesn't start with '/', 2) collapsing "."
and ".." segments and duplicate slashes, and 3) trimming any trailing slash
except for root; then replace the three inlined blocks (the current join logic
found around the path handling in useDataBinding.ts) with calls to this helper
for both reads and writes so all callers use the same normalized key space.
🪄 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: 08527a98-e6dd-44f3-a6d2-3b9404317da3

📥 Commits

Reviewing files that changed from the base of the PR and between 2c893d4 and 0940278.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (49)
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/catalog/README.md
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/core/BaseClient.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/A2UI.tsx
  • packages/genui/a2ui/src/react/A2UIProvider.tsx
  • packages/genui/a2ui/src/react/A2UIRenderer.tsx
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui/src/react/useDataBinding.ts
  • packages/genui/a2ui/src/store/MessageProcessor.ts
  • packages/genui/a2ui/src/store/Resource.ts
  • packages/genui/a2ui/src/store/SignalStore.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/createResource.test.ts
  • packages/genui/a2ui/test/processor.test.ts
💤 Files with no reviewable changes (24)
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/src/core/BaseClient.ts
✅ Files skipped from review due to trivial changes (9)
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/A2UIProvider.tsx
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui/src/react/A2UI.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/processor.test.ts
  • packages/genui/a2ui/src/store/Resource.ts
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/test/createResource.test.ts

Comment thread packages/genui/a2ui/src/catalog/README.md
Comment thread packages/genui/a2ui/src/react/A2UIRenderer.tsx Outdated
Comment thread packages/genui/a2ui/src/react/useDataBinding.ts
Comment thread packages/genui/a2ui/src/store/MessageProcessor.ts
Comment thread packages/genui/a2ui/src/store/MessageProcessor.ts Outdated
@PupilTong PupilTong force-pushed the claude/quirky-merkle-react branch from 0940278 to 3b57ae5 Compare May 8, 2026 06:20
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.

♻️ Duplicate comments (2)
packages/genui/a2ui/src/react/useDataBinding.ts (1)

87-89: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

fallbackValue is still ignored on the bound-path branch.

When path is provided but the signal has no resolved value yet, this returns undefined and never consults the caller-supplied fallbackValue, so controlled props start empty even though the hook’s API advertises a fallback. This was raised on a prior commit and the code is unchanged.

💡 Suggested fix
   const currentValue = path
-    ? (signalValue ?? initialValue)
+    ? (signalValue ?? initialValue ?? fallbackValue)
     : (initialValue ?? fallbackValue);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/genui/a2ui/src/react/useDataBinding.ts` around lines 87 - 89, The
bound-path branch for currentValue ignores fallbackValue; when path is truthy
and signalValue is undefined you must fall back to initialValue and then to
fallbackValue. Update the logic that computes currentValue (the ternary using
path, signalValue, initialValue, fallbackValue) so the path-true expression uses
signalValue ?? initialValue ?? fallbackValue (while keeping the existing
path-false expression as initialValue ?? fallbackValue) to ensure the
caller-supplied fallbackValue is respected.
packages/genui/a2ui/src/react/A2UIRenderer.tsx (1)

106-111: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

?? still prevents renderFallback/renderError from intentionally returning null.

renderFallback?.() and renderError?.(error) typed to return ReactNode allow consumers to suppress the built-in loading/error UI with an explicit null, but ?? treats null as "no override" and falls through to <DefaultLoading> / the error text. This was raised on a prior commit and the code is unchanged.

💡 Suggested fix
-  if (status === 'pending' && data === undefined) {
-    return renderFallback?.() ?? <DefaultLoading id={resource.id} />;
-  }
-
-  if (status === 'error') {
-    return renderError?.(error) ?? <text>Error: {String(error)}</text>;
-  }
+  if (status === 'pending' && data === undefined) {
+    const fb = renderFallback?.();
+    return fb !== undefined ? fb : <DefaultLoading id={resource.id} />;
+  }
+
+  if (status === 'error') {
+    const er = renderError?.(error);
+    return er !== undefined ? er : <text>Error: {String(error)}</text>;
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/genui/a2ui/src/react/A2UIRenderer.tsx` around lines 106 - 111, The
current use of the nullish coalescing operator (??) causes explicit null returns
from renderFallback or renderError to be ignored; update the logic in
A2UIRenderer to call renderFallback?.() and renderError?.(error), store each
result (e.g., fallbackNode and errorNode) and then check for !== undefined to
decide whether to return the consumer-provided node (including null) or fall
back to the built-ins (DefaultLoading with resource.id and the error text).
Ensure you reference renderFallback, renderError, DefaultLoading, resource.id
and error when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/genui/a2ui/src/react/A2UIRenderer.tsx`:
- Around line 106-111: The current use of the nullish coalescing operator (??)
causes explicit null returns from renderFallback or renderError to be ignored;
update the logic in A2UIRenderer to call renderFallback?.() and
renderError?.(error), store each result (e.g., fallbackNode and errorNode) and
then check for !== undefined to decide whether to return the consumer-provided
node (including null) or fall back to the built-ins (DefaultLoading with
resource.id and the error text). Ensure you reference renderFallback,
renderError, DefaultLoading, resource.id and error when making this change.

In `@packages/genui/a2ui/src/react/useDataBinding.ts`:
- Around line 87-89: The bound-path branch for currentValue ignores
fallbackValue; when path is truthy and signalValue is undefined you must fall
back to initialValue and then to fallbackValue. Update the logic that computes
currentValue (the ternary using path, signalValue, initialValue, fallbackValue)
so the path-true expression uses signalValue ?? initialValue ?? fallbackValue
(while keeping the existing path-false expression as initialValue ??
fallbackValue) to ensure the caller-supplied fallbackValue is respected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7e3392f2-47bc-4f03-a591-cbee2d8b5091

📥 Commits

Reviewing files that changed from the base of the PR and between 0940278 and 3b57ae5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (49)
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/catalog/README.md
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/core/BaseClient.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/A2UI.tsx
  • packages/genui/a2ui/src/react/A2UIProvider.tsx
  • packages/genui/a2ui/src/react/A2UIRenderer.tsx
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui/src/react/useDataBinding.ts
  • packages/genui/a2ui/src/store/MessageProcessor.ts
  • packages/genui/a2ui/src/store/Resource.ts
  • packages/genui/a2ui/src/store/SignalStore.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/createResource.test.ts
  • packages/genui/a2ui/test/processor.test.ts
💤 Files with no reviewable changes (24)
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/core/BaseClient.ts
✅ Files skipped from review due to trivial changes (8)
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/test/processor.test.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/store/MessageProcessor.ts
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui-playground/lynx-src/App.tsx
  • packages/genui/a2ui/src/catalog/README.md
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/react/A2UI.tsx
  • packages/genui/a2ui/test/createResource.test.ts
  • packages/genui/a2ui/src/store/Resource.ts

@PupilTong PupilTong force-pushed the claude/quirky-merkle-react branch from 3b57ae5 to 8c6ecfa Compare May 8, 2026 12:43
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.

🧹 Nitpick comments (1)
packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx (1)

239-293: ⚡ Quick win

Two small effect cleanups: drop the write-only storeRef, and reset store on teardown.

  1. storeRef is declared at Line 239, written at Line 278 and Line 290, but never read anywhere in the file — it's dead state. Either remove it, or actually use it (e.g., for the cleanup's stop bookkeeping if you intended to access the previous store).

  2. Cleanup nulls agentRef.current but doesn't reset the store state. When effectiveData changes, the old <A2UI> keeps rendering against the previous MessageStore until the new run() finishes its await Promise.all(...) and calls setStore(next). In that window the user sees stale content and any clicks are silently dropped because onAction forwards to agentRef.current?.onAction(action) which is now null. Calling setStore(null) in cleanup makes the loading state honest and avoids the lost-action footgun.

♻️ Proposed cleanup
-  const storeRef = useRef<MessageStore | null>(null);
   const agentRef = useRef<ReturnType<typeof createMockAgent> | null>(null);
   const [store, setStore] = useState<MessageStore | null>(null);
@@
       agentRef.current?.stop();
-      storeRef.current = next;
       agentRef.current = agent;
       setStore(next);
@@
     return () => {
       cancelled = true;
       agentRef.current?.stop();
-      storeRef.current = null;
       agentRef.current = null;
+      setStore(null);
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx` around lines 239 - 293,
storeRef is write-only and unused, and the effect cleanup doesn't reset the
React state store causing stale UI and dropped actions; remove the unused
storeRef (delete the declaration storeRef and the assignments to
storeRef.current in run and teardown) and update the effect cleanup to call
setStore(null) (in addition to agentRef.current?.stop() and nulling agentRef) so
the component shows the loading/empty state while the new run() loads; keep
existing agent creation via createMessageStore() and agentRef handling
(createMockAgent, agent.start/stop) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx`:
- Around line 239-293: storeRef is write-only and unused, and the effect cleanup
doesn't reset the React state store causing stale UI and dropped actions; remove
the unused storeRef (delete the declaration storeRef and the assignments to
storeRef.current in run and teardown) and update the effect cleanup to call
setStore(null) (in addition to agentRef.current?.stop() and nulling agentRef) so
the component shows the loading/empty state while the new run() loads; keep
existing agent creation via createMessageStore() and agentRef handling
(createMockAgent, agent.start/stop) intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05ad8bc4-f7a6-4540-8308-a9e2a5812f98

📥 Commits

Reviewing files that changed from the base of the PR and between 3b57ae5 and 8c6ecfa.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (49)
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/catalog/README.md
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/core/BaseClient.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/A2UI.tsx
  • packages/genui/a2ui/src/react/A2UIProvider.tsx
  • packages/genui/a2ui/src/react/A2UIRenderer.tsx
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui/src/react/useDataBinding.ts
  • packages/genui/a2ui/src/store/MessageProcessor.ts
  • packages/genui/a2ui/src/store/Resource.ts
  • packages/genui/a2ui/src/store/SignalStore.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/createResource.test.ts
  • packages/genui/a2ui/test/processor.test.ts
💤 Files with no reviewable changes (24)
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/Image.ts
  • packages/genui/a2ui/src/catalog/Row.ts
  • packages/genui/a2ui/src/chat/index.ts
  • packages/genui/a2ui/src/core/ComponentRegistry.ts
  • packages/genui/a2ui/src/catalog/Button.ts
  • packages/genui/a2ui/src/catalog/List.ts
  • packages/genui/a2ui/src/utils/index.ts
  • packages/genui/a2ui/src/core/index.ts
  • packages/genui/a2ui/src/utils/ComponentRegistry.ts
  • packages/genui/a2ui/src/catalog/Column.ts
  • packages/genui/a2ui/src/core/types.ts
  • packages/genui/a2ui/src/catalog/Card.ts
  • packages/genui/a2ui/src/catalog/CheckBox.ts
  • packages/genui/a2ui/src/catalog/RadioGroup.ts
  • packages/genui/a2ui/src/utils/createResource.ts
  • packages/genui/a2ui/src/core/A2UIRender.tsx
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/catalog/Text.ts
  • packages/genui/a2ui/src/chat/useLynxClient.ts
  • packages/genui/a2ui/src/catalog/Divider.ts
  • packages/genui/a2ui/src/chat/Conversation.tsx
  • packages/genui/a2ui/src/core/useDataBinding.ts
  • packages/genui/a2ui/src/core/BaseClient.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui-playground/lynx-src/tsconfig.json
  • packages/genui/a2ui/test/catalog.test.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • packages/genui/a2ui/src/react/A2UIProvider.tsx
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useA2UIContext.ts
  • packages/genui/a2ui/src/react/useCatalog.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui-playground/examples/io-mock/mockAgent.ts
  • packages/genui/a2ui/src/store/Resource.ts
  • packages/genui/a2ui-playground/examples/io-sse/sseAgent.ts
  • packages/genui/a2ui/src/store/types.ts
  • packages/genui/a2ui/src/react/A2UIRenderer.tsx
  • packages/genui/a2ui/src/react/useAction.ts
  • packages/genui/a2ui/test/createResource.test.ts
  • packages/genui/a2ui/src/store/MessageProcessor.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/test/processor.test.ts
  • packages/genui/a2ui/src/catalog/README.md
  • packages/genui/a2ui/src/react/useDataBinding.ts
  • packages/genui/a2ui/src/react/A2UI.tsx

@PupilTong PupilTong force-pushed the claude/quirky-merkle-react branch from 8c6ecfa to 1b63b1c Compare May 8, 2026 13:32
Comment thread packages/genui/a2ui-playground/examples/README.md
Comment thread packages/genui/a2ui-playground/examples/README.md
Comment thread packages/genui/a2ui/package.json
Comment thread packages/genui/a2ui/package.json
@PupilTong PupilTong force-pushed the claude/quirky-merkle-react branch 6 times, most recently from c204e52 to 970ad4d Compare May 11, 2026 13:08
@PupilTong PupilTong requested a review from Sherry-hue May 11, 2026 13:24
Build the public React surface on top of the message buffer + surface
processor introduced in the previous PR. The renderer is headless:
it ships no styles or chrome, and consumers wrap surfaces themselves.

- `<A2UI>`: all-in-one component that owns a `MessageProcessor` per
  mount, subscribes to the developer's `MessageStore`, processes new
  tail messages each render, and renders the most recent surface.
- `<A2UIRenderer>` / `NodeRenderer`: lower-level building blocks for
  consumers that want manual control over surface lifecycle.
- `<A2UIProvider>`: internal context carrying the active processor
  + catalog map.
- `useAction` / `useDataBinding` / `useCatalog`: hooks the catalog
  components reach for; resolve actions, data bindings, and catalog
  lookups against the current provider.

Catalog migration:
- Built-in components (`Text`, `Button`, `Card`, `Column`, `List`,
  `Row`, `CheckBox`, `RadioGroup`, `Image`, `Divider`) move from
  `core/A2UIRender` + global `componentRegistry` to the new
  `react/A2UIRenderer`. Side-effect re-exports (`<Name>.ts`,
  `all.ts`) and the global registry are dropped — every consumer
  composes via `defineCatalog([...])`.
- New public surface in `src/index.ts`; `package.json` exports
  trimmed accordingly (`./core`, `./chat`, `./catalog/all` removed;
  `./catalog/<Name>/catalog.json` subpaths kept for the manifest
  imports).

Cleanup:
- Delete `src/core/` (BaseClient, A2UIRender, ComponentRegistry,
  processor, types, useAction, useDataBinding) — replaced by the
  new layered design.
- Delete `src/chat/` (Conversation, useLynxClient) — now lives as
  an example pattern in the playground README, not as a package
  export.
- Delete `src/utils/` (ComponentRegistry, SignalStore,
  createResource) — replaced by the typed `src/store/` equivalents.

Playground:
- `lynx-src/App.tsx` switches from the old `BaseClient` +
  `A2UIRender` flow to `<A2UI messageStore={...} catalogs={...}>`
  with the per-component manifest tuple form.
- `examples/io-mock/` and `examples/io-sse/` show how to push raw
  protocol messages into a `MessageStore` from a developer-owned IO
  module (mock + SSE transports).
- `lynx-src/tsconfig.json` enables `resolveJsonModule` so the
  catalog manifest imports resolve.

Stacks on top of `feat(a2ui): add headless message buffer and surface
processor`. Once that lands and main rebases here, the diff is just
the React layer + catalog migration + playground.
@PupilTong PupilTong force-pushed the claude/quirky-merkle-react branch from 970ad4d to c60c802 Compare May 11, 2026 14:02
@PupilTong PupilTong merged commit 0c236ce into lynx-family:main May 12, 2026
83 of 85 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants