feat: add A2UI playground init#2472
Conversation
|
5155489 to
cd4495c
Compare
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (17)
pnpm-workspace.yaml (1)
27-27: Nit: keeppackages/*entries grouped/sorted.The existing
packages/*globs are loosely grouped together (lines 4–25); appendingpackages/genui/*afterwebsitebreaks that grouping. Consider moving it next to the otherpackages/*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 createssurface_sh_restaurants) is always loaded first as the default initial data, andaction_book.jsonis only dispatched when aUSER_ACTIONis detected inEventSource.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., "Requirescard.jsonto be loaded first to createsurface_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 addingsandboxandloading="lazy"to the preview iframe.The iframe embeds arbitrary render URLs (including potentially user-entered
demoUrlvalues passed throughbuildRenderUrl). Adding asandboxattribute 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, andloading="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: Consideraria-pressedfor toggle semantics.For a protocol toggle, adding
aria-pressed={value === '0.x'}on each button (or switching torole="radio"inside arole="radiogroup") would make the active state announced correctly by assistive tech — the currentactiveclass 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; thenpm: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-domon 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 emptycomponentNametoundefined.For a hash like
#/components/(trailing slash) or#/components//foo,parts[1]is an empty string rather thanundefined, which then propagates ascomponentName=""toComponentsPage. 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,
scrollIntoViewfires on every render (including parent re-renders caused byprotocolchanges or input typing, not just whenmessageschanges). 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: Unusedprotocolprop.
_propsis never read. Either wireprotocolinto 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,
initialisnull, so on first mount this assignslynxView.initData = {}and callsreload()before any real init arrives viapostMessage. Consider guarding to skip the null/empty case so thelynx-viewdoesn'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 fromgetActionMocks09.The 0.9 version accepts an optional
actionparameter and returns a filtered single-entry map when provided. This 0.8 version always returns the fullactionMockMap, 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: Inconsistentvariantfield on some 0.9 Text entries.
cta-text(line 262–264) andbtn-text(line 371–375) omitvariant, while other Text nodes in the same demos usevariant: 'body'/'h2'/'caption'. Either addvariant: '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.
emitSequenceis started from the constructor. It awaits a 10 ms timer before the firstemit, so in practice callers thataddEventListenersynchronously afternew EventSource(...)are fine — but this is fragile and not how the realEventSourcebehaves. Consider deferring the initialsetTimeoutby usingqueueMicrotask(() => 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 explicitundefined.
mock: string | undefinedcan be simplified tomock?: stringfor consistency withgetActionMocks09(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: RawString(e)in user-facing error.
setError(\Invalid JSON: ${String(e)}`)surfaces something likeInvalid JSON: SyntaxError: Unexpected token …. Usinge 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: Prefere instanceof Error ? e.message : String(e).Same note as
DemosPage.tsx:String(e)yields a noisySyntaxError: …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 betweenComponentGridandComponentsPage.
ComponentGridfiltersCOMPONENT_CATALOGper-category on every render, while the parent already computesgroupedByCategory(lines 102–109). Consider passinggroupedByCategorydown (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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (48)
packages/genui/a2ui-playground/package.jsonpackages/genui/a2ui-playground/rsbuild.config.tspackages/genui/a2ui-playground/src/App.tsxpackages/genui/a2ui-playground/src/componentCatalog.tspackages/genui/a2ui-playground/src/components/Chip.tsxpackages/genui/a2ui-playground/src/components/MobilePreview.tsxpackages/genui/a2ui-playground/src/components/ProtocolSwitch.tsxpackages/genui/a2ui-playground/src/components/QrCode.tsxpackages/genui/a2ui-playground/src/components/UsageSection.tsxpackages/genui/a2ui-playground/src/demos.tspackages/genui/a2ui-playground/src/entry.tsxpackages/genui/a2ui-playground/src/globals.d.tspackages/genui/a2ui-playground/src/mock/EventSource.tspackages/genui/a2ui-playground/src/mock/messages/0.8/action_book.jsonpackages/genui/a2ui-playground/src/mock/messages/0.8/card.jsonpackages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_abs_08.jsonpackages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_list_update_08.jsonpackages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_nested_08.jsonpackages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_numbers_booleans_08.jsonpackages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_relative_root_08.jsonpackages/genui/a2ui-playground/src/mock/messages/0.8/data_binding_update_08.jsonpackages/genui/a2ui-playground/src/mock/messages/0.8/radio_group_08.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/action_book.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/card.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_abs.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_list.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_list_update.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_nested.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_numbers_booleans.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_relative_root.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/data_binding_update.jsonpackages/genui/a2ui-playground/src/mock/messages/0.9/radio_group_09.jsonpackages/genui/a2ui-playground/src/mock/mock-data-manager-08.tspackages/genui/a2ui-playground/src/mock/mock-data-manager-09.tspackages/genui/a2ui-playground/src/pages/AIChatPage.tsxpackages/genui/a2ui-playground/src/pages/ComponentsPage.tsxpackages/genui/a2ui-playground/src/pages/DemosPage.tsxpackages/genui/a2ui-playground/src/pages/Dynamic.tsxpackages/genui/a2ui-playground/src/pages/Home.tsxpackages/genui/a2ui-playground/src/pages/Static.tsxpackages/genui/a2ui-playground/src/render.tsxpackages/genui/a2ui-playground/src/styles.csspackages/genui/a2ui-playground/src/utils/base64url.tspackages/genui/a2ui-playground/src/utils/demoUrl.tspackages/genui/a2ui-playground/src/utils/protocol.tspackages/genui/a2ui-playground/src/utils/renderUrl.tspackages/genui/a2ui-playground/tsconfig.jsonpnpm-workspace.yaml
Merging this PR will not alter performance
Comparing Footnotes
|
React External#730 Bundle Size — 680.2KiB (0%).1d913db(current) vs 647334c main#721(baseline) Bundle metrics
|
| Current #730 |
Baseline #721 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
17 |
17 |
|
5 |
5 |
|
8.59% |
8.59% |
|
0 |
0 |
|
0 |
0 |
Bundle analysis report Branch Sherry-hue:feat/a2ui-playground Project dashboard
Generated by RelativeCI Documentation Report issue
React MTF Example#745 Bundle Size — 196.47KiB (0%).1d913db(current) vs 647334c main#736(baseline) Bundle metrics
|
| Current #745 |
Baseline #736 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
173 |
173 |
|
66 |
66 |
|
44.07% |
44.07% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #745 |
Baseline #736 |
|
|---|---|---|
111.23KiB |
111.23KiB |
|
85.24KiB |
85.24KiB |
Bundle analysis report Branch Sherry-hue:feat/a2ui-playground Project dashboard
Generated by RelativeCI Documentation Report issue
React Example#7613 Bundle Size — 225.31KiB (0%).1d913db(current) vs 647334c main#7604(baseline) Bundle metrics
|
| Current #7613 |
Baseline #7604 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
4 |
4 |
|
179 |
179 |
|
69 |
69 |
|
44.57% |
44.57% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #7613 |
Baseline #7604 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
79.55KiB |
79.55KiB |
Bundle analysis report Branch Sherry-hue:feat/a2ui-playground Project dashboard
Generated by RelativeCI Documentation Report issue
Web Explorer#9186 Bundle Size — 900.02KiB (0%).1d913db(current) vs 647334c main#9177(baseline) Bundle metrics
Bundle size by type
|
| Current #9186 |
Baseline #9177 |
|
|---|---|---|
495.88KiB |
495.88KiB |
|
401.92KiB |
401.92KiB |
|
2.22KiB |
2.22KiB |
Bundle analysis report Branch Sherry-hue:feat/a2ui-playground Project dashboard
Generated by RelativeCI Documentation Report issue
cd4495c to
93c6160
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (5)
packages/genui/a2ui-playground/src/styles.css (2)
913-920:⚠️ Potential issue | 🟡 MinorStylelint:
word-break: break-wordis deprecated.Replace with
overflow-wrap: anywhere(oroverflow-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 | 🟡 MinorStylelint
value-keyword-casestill failing on unquoted font-family keywords.The unquoted identifiers
BlinkMacSystemFont,Roboto,Oxygen,Ubuntu,Cantarell,SFMono-Regular,Menlo,Consolasmust 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 | 🟡 MinorStatic 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, andAccordionare not inSUPPORTED_COMPONENTS(seepackages/genui/a2ui-playground/src/demos.ts). The home hero still advertises components the playground doesn't support. Derive this list fromSUPPORTED_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 | 🟡 MinorRefresh custom JSON when the protocol changes.
customJsonis initialized once from the initialprotocol; after switching protocols, entering Custom JSON can still show the old protocol payload unless the user manually clicks Fill Example.Consider syncing it on
protocolchanges 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 | 🟠 MajorKeep the selected scenario and editor in sync on protocol changes.
This still re-renders
ALL_SCENARIOS[0]instead ofcurrentScenario, and it does not updatecustomJson, 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),selectedCompisundefinedand 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) whencomponentNameis 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: UnclearedsetTimeoutcan setState after unmount.If the user navigates away within 600ms of sending, the pending timer will call
setMessageson 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 anAbortController-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., aChip) 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.8andv0.9, but this module hard-codesProtocolVersion = '0.9'andnormalizeProtocolignores its input and always returns'0.9'. This forcesProtocolSwitchto render only one button and makes the_valueparameter dead code. If v0.8 support is intentionally deferred, consider either:
- dropping the switch/normalizer entirely until a second version lands, or
- widening
ProtocolVersionto'0.8' | '0.9'now and actually honoringvalueinnormalizeProtocol(falling back toDEFAULT_PROTOCOLon 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 overanyforclientRef.
BaseClientis already imported at line 4; typing the ref asBaseClient | nullremoves 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
📒 Files selected for processing (39)
packages/genui/a2ui-playground/lynx-src/App.tsxpackages/genui/a2ui-playground/lynx-src/index.csspackages/genui/a2ui-playground/lynx-src/index.tsxpackages/genui/a2ui-playground/lynx-src/tsconfig.jsonpackages/genui/a2ui-playground/lynx.config.tspackages/genui/a2ui-playground/package.jsonpackages/genui/a2ui-playground/rsbuild.config.tspackages/genui/a2ui-playground/src/App.tsxpackages/genui/a2ui-playground/src/componentCatalog.tspackages/genui/a2ui-playground/src/components/Chip.tsxpackages/genui/a2ui-playground/src/components/MobilePreview.tsxpackages/genui/a2ui-playground/src/components/ProtocolSwitch.tsxpackages/genui/a2ui-playground/src/components/QrCode.tsxpackages/genui/a2ui-playground/src/components/UsageSection.tsxpackages/genui/a2ui-playground/src/demos.tspackages/genui/a2ui-playground/src/entry.tsxpackages/genui/a2ui-playground/src/globals.d.tspackages/genui/a2ui-playground/src/mock/EventSource.tspackages/genui/a2ui-playground/src/mock/messages/cast-grid.jsonpackages/genui/a2ui-playground/src/mock/messages/citywalk-list.jsonpackages/genui/a2ui-playground/src/mock/messages/recs.jsonpackages/genui/a2ui-playground/src/pages/AIChatPage.tsxpackages/genui/a2ui-playground/src/pages/ComponentsPage.tsxpackages/genui/a2ui-playground/src/pages/DemosPage.tsxpackages/genui/a2ui-playground/src/pages/Dynamic.tsxpackages/genui/a2ui-playground/src/pages/Home.tsxpackages/genui/a2ui-playground/src/pages/Static.tsxpackages/genui/a2ui-playground/src/render.tsxpackages/genui/a2ui-playground/src/styles.csspackages/genui/a2ui-playground/src/utils/base64url.tspackages/genui/a2ui-playground/src/utils/demoUrl.tspackages/genui/a2ui-playground/src/utils/protocol.tspackages/genui/a2ui-playground/src/utils/renderUrl.tspackages/genui/a2ui-playground/tsconfig.jsonpackages/genui/a2ui/src/catalog/Column/index.tsxpackages/genui/a2ui/src/catalog/Image/style.csspackages/genui/a2ui/src/catalog/List/index.tsxpackages/genui/a2ui/src/catalog/Row/index.tsxpackages/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
93c6160 to
9c2733c
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)
77-82:⚠️ Potential issue | 🟠 MajorEffect hardcodes
ALL_SCENARIOS[0]and does not resync the editor on protocol change.When
protocolchanges, this effect always re-renders the first scenario regardless of the user's current selection, andcustomJsonis 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 | 🟡 MinorReplace deprecated
word-break: break-word.Line 919 uses a deprecated keyword; switch to
overflow-wrapto 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 | 🟡 MinorUnresolved 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: Parameterjsonshadows the importedjsonfrom@codemirror/lang-json.The callback parameter name
jsonshadows the module-level import used to buildjsonExtensions. It's not a bug today (the import is only referenced at line 27), but it's confusing and fragile if future edits reach forjson()inside this callback. Consider renaming tojsonString/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
📒 Files selected for processing (8)
packages/genui/a2ui-playground/src/demos.tspackages/genui/a2ui-playground/src/mock/messages/cast-grid.jsonpackages/genui/a2ui-playground/src/mock/messages/fridge-search.jsonpackages/genui/a2ui-playground/src/pages/DemosPage.tsxpackages/genui/a2ui-playground/src/styles.csspackages/genui/a2ui/src/catalog/Column/index.tsxpackages/genui/a2ui/src/catalog/Image/style.csspackages/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
ee452ea to
40e8cab
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
packages/genui/a2ui-playground/src/styles.css (2)
25-30:⚠️ Potential issue | 🟡 MinorStylelint still failing on
value-keyword-casefor unquoted system font identifiers.
BlinkMacSystemFont,Roboto,Oxygen,Ubuntu,Cantarell,SFMono-Regular,Menlo,Consolasare 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 | 🟡 MinorStylelint still failing:
word-break: break-wordis deprecated.Replace with
overflow-wrap: anywhere(preferred) oroverflow-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 mirrorsRowconsistently.Logic correctly guards on
typeof weight === 'number' && weight > 0, so0, negative,NaN, and missing values fall through to the unwrapped path.minHeight: 0is the right counterpart to Row'sminWidth: 0for column flex children, and readingweightfromchild(rather thanchildWithContext) is equivalent sincechildWithContextis 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 ond.messagesis misleading and not needed.
d.messagesis typed asunknown, but each mock payload is actually an object (e.g.{ messages: [...] }or{ beginRender, surfaceUpdate }), not aunknown[].Array.prototype.flatMapon 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, sincecollectComponentNamesFromMessagesalready handles arbitraryunknowninput.♻️ 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (43)
packages/genui/a2ui-playground/lynx-src/App.tsxpackages/genui/a2ui-playground/lynx-src/index.csspackages/genui/a2ui-playground/lynx-src/index.tsxpackages/genui/a2ui-playground/lynx-src/tsconfig.jsonpackages/genui/a2ui-playground/lynx.config.tspackages/genui/a2ui-playground/package.jsonpackages/genui/a2ui-playground/rsbuild.config.tspackages/genui/a2ui-playground/src/App.tsxpackages/genui/a2ui-playground/src/componentCatalog.tspackages/genui/a2ui-playground/src/components/Chip.tsxpackages/genui/a2ui-playground/src/components/MobilePreview.tsxpackages/genui/a2ui-playground/src/components/ProtocolSwitch.tsxpackages/genui/a2ui-playground/src/components/QrCode.tsxpackages/genui/a2ui-playground/src/components/UsageSection.tsxpackages/genui/a2ui-playground/src/demos.tspackages/genui/a2ui-playground/src/entry.tsxpackages/genui/a2ui-playground/src/globals.d.tspackages/genui/a2ui-playground/src/mock/EventSource.tspackages/genui/a2ui-playground/src/mock/messages/cast-grid.jsonpackages/genui/a2ui-playground/src/mock/messages/citywalk-list.jsonpackages/genui/a2ui-playground/src/mock/messages/fridge-search.jsonpackages/genui/a2ui-playground/src/mock/messages/recs.jsonpackages/genui/a2ui-playground/src/mock/messages/trip-planner.jsonpackages/genui/a2ui-playground/src/mock/messages/workout-plan.jsonpackages/genui/a2ui-playground/src/pages/AIChatPage.tsxpackages/genui/a2ui-playground/src/pages/ComponentsPage.tsxpackages/genui/a2ui-playground/src/pages/DemosPage.tsxpackages/genui/a2ui-playground/src/pages/Dynamic.tsxpackages/genui/a2ui-playground/src/pages/Home.tsxpackages/genui/a2ui-playground/src/pages/Static.tsxpackages/genui/a2ui-playground/src/render.tsxpackages/genui/a2ui-playground/src/styles.csspackages/genui/a2ui-playground/src/utils/base64url.tspackages/genui/a2ui-playground/src/utils/demoUrl.tspackages/genui/a2ui-playground/src/utils/protocol.tspackages/genui/a2ui-playground/src/utils/renderUrl.tspackages/genui/a2ui-playground/tsconfig.jsonpackages/genui/a2ui/src/catalog/Column/index.tsxpackages/genui/a2ui/src/catalog/Image/style.csspackages/genui/a2ui/src/catalog/List/index.tsxpackages/genui/a2ui/src/catalog/Row/index.tsxpackages/genui/a2ui/src/catalog/all.tspnpm-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
40e8cab to
a5c2b95
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
packages/genui/a2ui-playground/src/pages/Dynamic.tsx (2)
83-90:⚠️ Potential issue | 🟡 MinorCustom-mode render URL still drops preset
actionMocks.The past-review fix for this looks to have been reverted —
buildRenderUrlin the custom branch still omitsactionMocks, 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 | 🟡 MinorSegmented control still uses invalid
role='tablist'without tab children.The wrapping
divdeclaresrole='tablist'but the<button>s inside are notrole='tab'and have noaria-selected, producing invalid ARIA semantics. Switch to a toggle-group model witharia-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-wordis still flagged as deprecated by stylelint.Replace with
overflow-wrap: anywhere(oroverflow-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 | 🟡 MinorStylelint
value-keyword-casestill failing — unquoted font family keywords must be lowercased.Static analysis confirms these violations remain for
BlinkMacSystemFont,Roboto,Oxygen,Ubuntu,Cantarell,SFMono-Regular,Menlo, andConsolas.💅 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.componentIdand the resultingsurface.components.get(componentId)are constant for the lifetime of this render, so resolving them insideitems.mapdoes 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 emptycontentwhen 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, andd1-stop1-desc"Fetching notes..." → final text at lines 135/147). The remaining stops (d1-stop2/3, all of Day 2) andday2-weatherat line 250 are populated with their final text in one shot. Since this demo's stated purpose is "each stop filled progressively" (seeSTATIC_DEMOS.descriptioninpackages/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 increasingt=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 typingclientRefasBaseClientrather than usingany.
BaseClientis 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
processUserActionassignment 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-assignedmessageIdvalues if present in mocks.Currently, both lines 141–144 and 163–167 unconditionally overwrite
messageIdon every message. While all examined mock payloads lack pre-assignedmessageIdvalues and the current single-turn grouping appears intentional, the type definition (A2uiMessageallowsmessageId?: 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., asurfaceUpdatereferencing a priorbeginRender'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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (43)
packages/genui/a2ui-playground/lynx-src/App.tsxpackages/genui/a2ui-playground/lynx-src/index.csspackages/genui/a2ui-playground/lynx-src/index.tsxpackages/genui/a2ui-playground/lynx-src/tsconfig.jsonpackages/genui/a2ui-playground/lynx.config.tspackages/genui/a2ui-playground/package.jsonpackages/genui/a2ui-playground/rsbuild.config.tspackages/genui/a2ui-playground/src/App.tsxpackages/genui/a2ui-playground/src/componentCatalog.tspackages/genui/a2ui-playground/src/components/Chip.tsxpackages/genui/a2ui-playground/src/components/MobilePreview.tsxpackages/genui/a2ui-playground/src/components/ProtocolSwitch.tsxpackages/genui/a2ui-playground/src/components/QrCode.tsxpackages/genui/a2ui-playground/src/components/UsageSection.tsxpackages/genui/a2ui-playground/src/demos.tspackages/genui/a2ui-playground/src/entry.tsxpackages/genui/a2ui-playground/src/globals.d.tspackages/genui/a2ui-playground/src/mock/EventSource.tspackages/genui/a2ui-playground/src/mock/messages/cast-grid.jsonpackages/genui/a2ui-playground/src/mock/messages/citywalk-list.jsonpackages/genui/a2ui-playground/src/mock/messages/fridge-search.jsonpackages/genui/a2ui-playground/src/mock/messages/recs.jsonpackages/genui/a2ui-playground/src/mock/messages/trip-planner.jsonpackages/genui/a2ui-playground/src/mock/messages/workout-plan.jsonpackages/genui/a2ui-playground/src/pages/AIChatPage.tsxpackages/genui/a2ui-playground/src/pages/ComponentsPage.tsxpackages/genui/a2ui-playground/src/pages/DemosPage.tsxpackages/genui/a2ui-playground/src/pages/Dynamic.tsxpackages/genui/a2ui-playground/src/pages/Home.tsxpackages/genui/a2ui-playground/src/pages/Static.tsxpackages/genui/a2ui-playground/src/render.tsxpackages/genui/a2ui-playground/src/styles.csspackages/genui/a2ui-playground/src/utils/base64url.tspackages/genui/a2ui-playground/src/utils/demoUrl.tspackages/genui/a2ui-playground/src/utils/protocol.tspackages/genui/a2ui-playground/src/utils/renderUrl.tspackages/genui/a2ui-playground/tsconfig.jsonpackages/genui/a2ui/src/catalog/Column/index.tsxpackages/genui/a2ui/src/catalog/Image/style.csspackages/genui/a2ui/src/catalog/List/index.tsxpackages/genui/a2ui/src/catalog/Row/index.tsxpackages/genui/a2ui/src/catalog/all.tspnpm-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
aae5b4c to
dc45aed
Compare
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
dc45aed to
1d913db
Compare
feat: add playground new style
A2UI Playground (Init)
What
This PR introduces an
A2UI Playground(underpackages/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
Lynx Preview)Demos
Reset / Clear / RenderLynx Preview(right) + “View on Device” QR code for mobile previewComponents
Usagesnippets (per protocol version)How To Run
pnpm --filter a2ui-playground devProtocoltoggle (0.8 / 0.9) to compare behaviors.Follow-ups (Needs To Be Completed)
DEFAULT_DEMO_URLor a bundled demo artifact (currently empty, so rendering depends on follow-up work)Lynx Previewand enable QR-based “open on device”Notes
This PR focuses on establishing the Playground structure and core UX layout; functional completeness will be delivered incrementally in follow-up PRs.
Checklist
Summary by CodeRabbit
New Features
Improvements