chore: pick gen-ui exploration code#2471
Conversation
|
|
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:
📝 WalkthroughWalkthroughAdded a new package packages/genui/a2ui implementing an A2UI runtime: core types/processor/client/renderer, catalog components and styles, chat UI and hook, data-binding/action hooks, utility stores/registries, TypeScript configs, workspace entries, license, README, and CODEOWNERS. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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: 14
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (16)
packages/genui/a2ui/src/utils/createResource.ts-13-33 (1)
13-33:⚠️ Potential issue | 🟡 Minor
'error'status is unreachable — no rejection path exposed.
rejectis never captured from the Promise executor and there is nofail/errormethod on the returnedResource. The.catchbranch (lines 29–33) and thecase 'error'arm inread()therefore can never fire. Either:
- expose a
fail(err: unknown)method symmetric withcomplete, or- remove the unreachable error handling to avoid implying behavior that doesn't exist.
Given
BaseClientconsumes streamed processor events that can plausibly fail, exposing an explicit failure path is probably what you want.🛠 Suggested fix: expose `fail`
export interface Resource<T = unknown> { id: string; readonly completed: boolean; read: () => T; complete: (result: T) => void; + fail: (error: unknown) => void; onUpdate: (callback: (result: T) => void) => () => void; promise: Promise<T>; } export function createResource<T = unknown>(id: string): Resource<T> { let resolve: (value: T) => void; - const mockFn = new Promise<T>((_resolve) => { - resolve = _resolve; - }); + let reject: (reason: unknown) => void; + const mockFn = new Promise<T>((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); @@ + fail: (err: unknown) => { + if (status === 'pending') { + reject(err); + } + },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/utils/createResource.ts` around lines 13 - 33, The createResource function currently never exposes a rejection path so the 'error' status and the .catch branch in the internal promise (and the 'error' arm in read()) are unreachable; modify createResource to capture the Promise executor's reject function (in addition to resolve) and add a fail(err: unknown) method alongside complete that (1) calls the captured reject with err, (2) sets status = 'error' and error = err, and (3) notifies any listeners of the failure as appropriate so consumers (e.g., BaseClient) can trigger the .catch branch and the read() 'error' case.packages/genui/a2ui/src/catalog/List/catalog.json-38-46 (1)
38-46:⚠️ Potential issue | 🟡 MinorSchema declares
align, but the component never reads it.
packages/genui/a2ui/src/catalog/List/index.tsxonly destructureschildren,surface,dataContextPath, anddirectionfrom props —alignis advertised in the catalog schema but has no effect. Either implementalign(e.g., apply it via class name / style) inList/index.tsx, or remove it from the schema until it's wired up, otherwise consumers will set it expecting layout changes that never happen.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/List/catalog.json` around lines 38 - 46, The catalog advertises an align prop but List/index.tsx does not use it; update the List component to accept and apply the align prop (e.g., add align to the props destructuring in List/index.tsx and translate it into a CSS class or inline style on the root container to control cross-axis alignment) or remove the align entry from packages/genui/a2ui/src/catalog/List/catalog.json; to fix, modify List/index.tsx (where children, surface, dataContextPath, and direction are destructured) to also destructure align and map its values ("start","center","end","stretch") to the appropriate className or style so the advertised prop has effect.packages/genui/a2ui/src/catalog/Row/catalog.json-31-41 (1)
31-41:⚠️ Potential issue | 🟡 MinorSame
stretchmismatch asColumn.
Row.justifyenum contains"stretch", but the rendered classdistribution-stretchhas no rule instyle.css(onlydistribution-start/center/end/spaceBetween/spaceAround/spaceEvenlyare defined), andjustify-content: stretchis not a valid flex value. Remove"stretch"fromjustifyor add the missing CSS class to keep the schema and renderer consistent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Row/catalog.json` around lines 31 - 41, Row.justify currently includes "stretch", but the renderer uses a CSS class distribution-stretch which has no rule and justify-content: stretch is invalid; remove "stretch" from the Row.justify enum in catalog.json so the schema matches the defined classes (distribution-start/center/end/spaceBetween/spaceAround/spaceEvenly) and avoid producing an unsupported justify value, or alternatively add a valid CSS rule named distribution-stretch in style.css that maps to a legal flex behavior if you intentionally want a supported option; update either catalog.json (remove "stretch") or style.css (add valid distribution-stretch rule) to keep schema and renderer consistent.packages/genui/a2ui/package.json-4-4 (1)
4-4:⚠️ Potential issue | 🟡 Minor
"private"should be a boolean, not a string.
"private": "true"is a string literal. npm/pnpm expect"private": true(boolean) to mark the package as unpublishable. The string form is currently truthy in most tooling, but it's not the documented contract and may not be honored consistently.Proposed fix
- "private": "true", + "private": true,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/package.json` at line 4, The package.json currently sets the "private" field as a string ("private": "true"); change it to a boolean by replacing the string value with the boolean true ("private": true) so npm/pnpm correctly treat the package as unpublishable and conform to the documented contract.packages/genui/a2ui/src/catalog/Column/catalog.json-31-41 (1)
31-41:⚠️ Potential issue | 🟡 Minor
justifyenum includesstretchbut no matching CSS class / valid mapping.The
Columncomponent rendersdistribution-${justify}(seepackages/genui/a2ui/src/catalog/Column/index.tsx), andstyle.css(lines 26-42) definesdistribution-start/center/end/spaceBetween/spaceAround/spaceEvenly— there is nodistribution-stretchrule. A schema-valid value of"stretch"will produce a class with no styling (andjustify-content: stretchis not a standard flex value anyway). Either drop"stretch"from the enum or add the corresponding CSS rule.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Column/catalog.json` around lines 31 - 41, The schema for the Column `justify` enum includes an unsupported value "stretch" which produces a non-functional `distribution-stretch` class at render time (the component builds `distribution-${justify}`); fix by either removing "stretch" from the `justify` enum in catalog.json or by adding a corresponding CSS rule for `.distribution-stretch` in the Column stylesheet to map to a valid flex alignment (e.g., use `align-items: stretch` or an appropriate `justify-content`/layout behavior) so that `distribution-stretch` has a defined style; update whichever you choose consistently (catalog.json `justify` enum or Column `style.css`) and ensure the Column component (index.tsx) continues to generate `distribution-${justify}` classes without producing a no-op class.packages/genui/a2ui/src/catalog/Column/index.tsx-22-27 (1)
22-27:⚠️ Potential issue | 🟡 MinorSchema permits
justify: "stretch"but no matching CSS rule exists.
Column/catalog.jsondeclares"stretch"in thejustifyenum, yet neitherColumn/style.cssnorRow/style.cssdefines.distribution-stretch. If the server emitsjustify: "stretch", the rendered classNamedistribution-stretchwill be a no-op andjustify-contentwill fall back to its initial value. Either drop"stretch"from the schema enum or add the corresponding CSS rule.🤖 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 22 - 27, The schema allows justify: "stretch" but no CSS handles the generated class distribution-stretch, so either remove "stretch" from the Column/catalog.json justify enum or add a CSS rule for .distribution-stretch (and mirror in Row/style.css if Rows can also receive "stretch") that sets the appropriate justify-content value; update the Column component (the justify default/usage and the className generation that produces `distribution-${justify}`) only if you choose to change the schema so class names remain consistent with allowed enum values.packages/genui/a2ui/src/chat/useLynxClient.ts-33-66 (1)
33-66:⚠️ Potential issue | 🟡 MinorClient is created once but
urlchanges are ignored.
getClientdepends onurl, but the cachedclientRef.currentis only initialized on first call — subsequenturlchanges will keep returning the original client bound to the original URL. If the hook is expected to track URL changes, recreate the client whenurlchanges (e.g.useEffectto dispose/replace); otherwise consider documenting thaturlis captured once.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/chat/useLynxClient.ts` around lines 33 - 66, getClient currently caches a BaseClient instance in clientRef.current on first call and ignores later url changes; update the hook to recreate/dispose the client when url changes by adding a useEffect that watches url (and keepHistory if relevant), calls any cleanup method on clientRef.current (e.g., close/dispose) if present, clears or replaces clientRef.current, and reattaches the onResourceCreated and onResponseComplete handlers to the new BaseClient so getClient returns a client bound to the current url; ensure getClient/useCallback and clientRef management reflect this lifecycle change.packages/genui/a2ui/src/core/useAction.ts-34-40 (1)
34-40:⚠️ Potential issue | 🟡 MinorFalsy-but-valid signal values short-circuit the JSON parse path.
if (!raw) return raw;returns early for legitimate values like0,false, and"". If the store stringifies primitives as JSON (e.g."0","false",'""'), those would skipJSON.parseand leak raw JSON strings to callers. Consider narrowing the guard toraw == null(ortypeof raw !== 'string') so only truly absent values bypass the parse.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/useAction.ts` around lines 34 - 40, The early-return guard in useAction.ts incorrectly treats falsy-but-valid values as absent; change the check around the local `raw` (from `if (!raw) return raw;`) to only bypass parsing for truly missing/non-string values (e.g. `raw == null` or `typeof raw !== 'string'`) so that legitimate stringified primitives (like "0", "false", "" ) still go through `JSON.parse(raw)`; update the logic around the `signal.value` -> `raw` handling and preserve the existing try/catch that falls back to returning `raw` on parse errors.packages/genui/a2ui/src/chat/Conversation.tsx-38-51 (1)
38-51:⚠️ Potential issue | 🟡 Minor
useLynxClientis always invoked, even in controlled mode.When callers supply
messages/sendMessage,useLynxClient(url ?? '')still runs on every render, creating aBaseClientwith an empty base URL on first use. That client is never used in controlled mode but it allocates state, sets up refs, and would attempt requests if accidentally invoked. Consider gating the hook (e.g. split into two sub-components) or making the client creation lazy inuseLynxClientso a falsy/emptyurldoesn't construct a live client.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/chat/Conversation.tsx` around lines 38 - 51, The Conversation component always calls useLynxClient(url ?? '') even in controlled mode which creates an unused BaseClient; modify the component so the hook is only invoked when uncontrolled (url truthy) — either gate the hook call by moving the uncontrolled logic into a small UncontrolledConversation sub-component that calls useLynxClient(url) or change useLynxClient to lazily construct the BaseClient when a real non-empty url is provided; update references to messages/sendMessage to use props when provided and fall back to hookResult only when url is present (symbols: Conversation, useLynxClient, url, messages, sendMessage).packages/genui/a2ui/src/chat/Conversation.tsx-97-102 (1)
97-102:⚠️ Potential issue | 🟡 MinorNon-null assertion on optional
resourcecan crash at runtime.
Message.resourceis declared optional, butitem.resource!(line 98) will throw if an agent message is pushed without a resource (e.g. early user-visible messages, or errors). Consider guarding:- <A2UIRender - resource={item.resource!} - renderFallback={() => ( - <text className='loading-text'>Thinking...</text> - )} - /> + {item.resource + ? ( + <A2UIRender + resource={item.resource} + renderFallback={() => ( + <text className='loading-text'>Thinking...</text> + )} + /> + ) + : <text className='loading-text'>Thinking...</text>}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/chat/Conversation.tsx` around lines 97 - 102, The code uses a non-null assertion on the optional Message.resource (item.resource!) when rendering A2UIRender which can crash if resource is undefined; change the render so you only call A2UIRender when resource exists (e.g., guard with if/item.resource conditional or conditional JSX: item.resource ? <A2UIRender resource={item.resource} renderFallback={...}/> : <text className='loading-text'>Thinking...</text>), or pass a safe value via optional chaining and let the fallback handle it; reference symbols: A2UIRender, Message.resource (item.resource) in Conversation.tsx.packages/genui/a2ui/src/core/BaseClient.ts-358-410 (1)
358-410:⚠️ Potential issue | 🟡 MinorNo cleanup path for the opened
EventSource.
startStreamingconstructs anEventSourcebut the returned object exposes no way to abort/close it. If the consumer drops the promise (component unmount, user cancels), the SSE connection continues untilcompleteorerror, holding a server slot and potentially mutating surface state after the UI has moved on. Consider returning anabort()/AbortControllerhandle alongsidestartStreaming, and callingeventSource.close()on abort.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/BaseClient.ts` around lines 358 - 410, startStreaming currently creates an EventSource (via EventSourceImpl and eventSource) but never exposes a cleanup path; modify startStreaming to return an object with an abort() method (or an AbortController) that calls eventSource.close(), removes any attached listeners, and rejects/cleanup the streaming promise so callers can cancel (e.g., on unmount). Ensure the returned abort hook is wired into all places that resolve/complete the stream so eventSource.close() is called on manual abort, on error, and on normal completion to avoid leaking server connections or mutating state after cancellation.packages/genui/a2ui/src/core/BaseClient.ts-342-360 (1)
342-360:⚠️ Potential issue | 🟡 MinorUser content is streamed over a GET query string.
buildSseParamsplacestext(potentially long user input,userActionJSON context, session IDs) directly into the SSE URL. This has three downsides worth confirming are acceptable for this integration:
- Server access logs, proxies, and browser history retain arbitrary user content.
- The 2048-char warning (Line 392-395) only warns; it does not truncate or fall back to POST.
sessionIdcontaining auth material would leak through the same channels.If this endpoint supports POST + SSE (e.g. via
fetchstreaming or a POST-then-redirect pattern), prefer that for anything user-generated.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/BaseClient.ts` around lines 342 - 360, The send() implementation streams user content in the SSE GET query (built by buildSseParams) which can leak long or sensitive data; change startStreaming()/send() to detect sensitive or oversized payloads (e.g., presence of sessionId or length > 2048) and fallback to a POST-based streaming approach (use fetch POST with body and SSE-compatible response or POST-then-redirect) instead of placing text/userAction/sessionId in the URL; update buildSseParams to exclude sessionId and large text when called for GET and ensure send() chooses GET only for safe/short, non-sensitive payloads, or else switches to the POST streaming path.packages/genui/a2ui/src/core/A2UIRender.tsx-129-131 (1)
129-131:⚠️ Potential issue | 🟡 MinorError rendering double-prefixes "Error:".
String(new Error('boom'))yields"Error: boom", so the UI displaysError: Error: boom. Rendererror.messageinstead.- if (error) { - return <text>Error: {String(error)}</text>; - } + if (error) { + return <text>Error: {error.message}</text>; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/A2UIRender.tsx` around lines 129 - 131, The UI prints duplicate "Error:" because A2UIRender.tsx uses String(error) inside the Error branch; change the rendering in the error handling of the A2UIRender component to use error.message (with a safe fallback for non-Error values), e.g. render Error: {error?.message ?? String(error)} so Error objects show a single "Error: <message>" while still handling other types.packages/genui/a2ui/src/core/A2UIRender.tsx-81-123 (1)
81-123:⚠️ Potential issue | 🟡 Minor
loading/errorstate is stale acrossresourcechanges.When the
resourceprop changes,useStatevalues persist from the previous resource. If the previous resource resolved (loading=false) and the new one is still pending, the effect will not short-circuit but the initial render will still show non-loading UI untilsetLoadingfires. Similarly, a previouserrorwill render the error UI briefly against the new resource.Reset
loading/errorsynchronously whenresourcechanges — either by resetting inside the effect's first synchronous pass, or by deriving state fromresourcevia a key/keyed reducer:🛠️ Suggested change
useEffect(() => { let active = true; + setError(null); + setLoading(!resource.completed); if (resource.completed) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/A2UIRender.tsx` around lines 81 - 123, When the resource prop changes A2UIRender's effect must synchronously reset derived state to avoid showing stale loading/error; at the start of the useEffect (before async reads/promises) call setLoading(true) and setError(undefined) (and optionally setData(undefined) if you want blank UI) so the initial render reflects the new resource, then proceed with the existing resource.read()/resource.promise logic and subscription (refer to useEffect, resource, resource.read, resource.promise, resource.onUpdate, setLoading, setError, setData, unsubscribe).packages/genui/a2ui/src/core/BaseClient.ts-420-433 (1)
420-433:⚠️ Potential issue | 🟡 MinorHard-coded 300 ms delay between message batches adds perceptible latency.
processQueuewaitsMESSAGE_PROCESS_DELAY(300 ms) between every batch, so a response with N SSE deltas takes at leastN × 300 msto render even when processing is instant. Consider making this configurable, using it only when animations need pacing, or usingrequestAnimationFrame/ yielding to the event loop (await Promise.resolve()) instead of a fixed timeout.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/BaseClient.ts` around lines 420 - 433, processQueue currently enforces a hard-coded MESSAGE_PROCESS_DELAY between batches causing N×300ms latency; change it to use a configurable delay and a non-blocking default: add a new option/field (e.g., messageProcessDelay) passed into the class or constructor, use await Promise.resolve() or requestAnimationFrame() when messageProcessDelay is 0 or "yield" to just yield to the event loop, and only call setTimeout with MESSAGE_PROCESS_DELAY when the configured delay > 0 (or when an explicit "paceAnimations" flag is true); update processQueue, MESSAGE_PROCESS_DELAY references, and any callers to use the new option and keep using this.processor.processMessages(msgs) unchanged.packages/genui/a2ui/src/core/useDataBinding.ts-118-124 (1)
118-124:⚠️ Potential issue | 🟡 MinorEffect re-creates on every render due to
propertiesidentity changes.
useResolvedPropsis invoked fromNodeRendererwithcomponent(the fullComponentInstance) asproperties(seeA2UIRender.tsxLine 186).componentis a React state value that is replaced with a fresh{ ...dataMap['component'] }on every surface update, and anything that recreatesNodeRenderer's parent produces a new object reference. Each render with a new reference tears down the signaleffectsubscription and re-runsresolvePropertiessynchronously, defeating the benefit of reactive tracking and causing extra renders.Consider memoizing
propertiesupstream or keying the effect on a stable signature (e.g.JSON.stringify(properties)or the set of bound paths) rather than referential identity.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/useDataBinding.ts` around lines 118 - 124, The effect in useDataBinding (the useEffect that creates the signal effect -> setResolved(resolveProperties(...))) is being re-created on every render because the `properties` object identity changes; fix by depending on a stable signature instead of the raw object or by memoizing upstream: either memoize `properties` in the caller (e.g. NodeRenderer/A2UIRender.tsx) so the same object reference is reused, or change the dependency for that useEffect to a stable key derived from `properties` (for example a JSON.stringify(properties) or a computed set of bound paths) so the effect only re-runs when actual property content changes; keep references to `surface` and `dataContextPath` as before and leave `resolveProperties`, `setResolved`, and `effect` usage unchanged.
🧹 Nitpick comments (29)
packages/genui/a2ui/src/catalog/Text/catalog.json (1)
1-41: Minor: consideradditionalProperties: falseon theTextroot.The inner path-binding object enforces
additionalProperties: false, but theTextschema itself does not, so unknown props (e.g. typos likevarient) silently validate. If this catalog is meant to be authoritative for component prop validation, tightening the root to reject unknown properties would catch authoring mistakes early.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Text/catalog.json` around lines 1 - 41, The Text schema currently allows unknown root properties which lets typos like "varient" pass; update the "Text" object in catalog.json to add "additionalProperties": false at the same level as "properties" and "required" so the root schema rejects unknown props (affects the "Text" schema that defines "text" and "variant" and the nested path-binding object).packages/genui/a2ui/src/utils/createResource.ts (1)
53-60:completeafter success silently bypasses the resolved promise.Once
status === 'success', subsequentcomplete(res)calls update the closed-overresultand notifylisteners, but thepromisehas already resolved with the prior value andread()returns the newresultsynchronously. That asymmetry means consumers awaitingresource.promisesee the first value, while consumers callingresource.read()(or subscribing viaonUpdate) see later values — easy to misuse.Worth either:
- documenting "first
completeresolves; latercompletecalls are streaming updates", or- splitting the API into
complete(res)(terminal) andupdate(res)(streaming) for clarity.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/utils/createResource.ts` around lines 53 - 60, The current complete function (complete) conflates terminal resolution and ongoing updates: when status === 'success' further complete(res) calls only update the closed-over result and notify listeners (listeners, result) but do not resolve the original promise (promise) again, causing inconsistent behavior between await resource.promise and resource.read()/onUpdate subscribers. Fix by splitting responsibilities: make complete(res) perform the one-time terminal resolve of promise and set status to 'success', and add a new update(res) method that sets result and notifies listeners without touching the promise; update any call sites that intend streaming updates to use update, and update read()/onUpdate to rely on result/listeners as before to preserve streaming behavior.packages/genui/a2ui/src/utils/SignalStore.ts (1)
21-26: Reference equality onupdatemay miss object/array mutations.
s.value !== valuewill skip the assignment when callers pass back the same object reference with mutated internals (common when consumers doobj.field = x; store.update(path, obj)). For Preact signals this means subscribers won't be notified. If the contract is "value is always replaced by-reference for reactivity", consider documenting it; otherwise drop the equality guard and let Preact's own dedupe handle primitives.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/utils/SignalStore.ts` around lines 21 - 26, The update method in SignalStore (update(path: string, value: unknown)) currently uses reference equality (s.value !== value) which misses in-place mutations of objects/arrays and prevents Preact signals from notifying subscribers; remove the reference-equality guard so the assignment always runs (i.e., always set s.value = value) and rely on Preact's own deduping for primitives, or alternatively document that update requires callers to replace by-reference if you intentionally keep the guard; locate the logic in the update function and change it accordingly.packages/genui/a2ui/src/catalog/List/style.css (1)
1-4: Hard-codedheight: 400pxon the list container.A fixed pixel height on a generic catalog component will clip or leave dead space across surfaces with different layouts. Consider exposing height via a prop/CSS custom property, or letting the parent layout drive sizing (e.g.,
flex: 1/height: 100%).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/List/style.css` around lines 1 - 4, The .list CSS rule currently hard-codes height: 400px which breaks flexible layouts; change .list to accept a CSS custom property and flexible sizing (for example replace height: 400px with height: var(--list-height, auto); and add optional flex behavior like flex: 1; or height: 100% so parent layouts can drive size), and update any consuming components to set --list-height or rely on parent flexbox sizing as needed.packages/genui/a2ui/src/catalog/Card.ts (1)
8-8: Unsafe double cast pattern repeated across all catalog components.The
as unknown as ComponentRenderercast appears in 10+ catalog files (Card, List, Row, Image, Divider, Column, Text, Button, RadioGroup, CheckBox, and others), all using identical code on line 8 or 10. This pattern defeats TypeScript's type checking and could mask runtime prop-shape bugs if component signatures diverge from whatComponentRenderer(which expects{id, surfaceId, surface, component}) actually receives.Introduce a typed registration adapter (e.g.,
register<P>(name: string, component: ComponentType<P>)) that internally handles the cast once, eliminating the need for it at each catalog entry.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Card.ts` at line 8, The catalog files use an unsafe "as unknown as ComponentRenderer" double-cast on each component (e.g., Card) when calling componentRegistry.register; create a single typed adapter on componentRegistry such as register<P>(name: string, component: ComponentType<P>) that internally performs the one safe cast to ComponentRenderer, then replace calls like componentRegistry.register('Card', Card as unknown as ComponentRenderer) with componentRegistry.register('Card', Card) and remove the double-cast from all catalog entries; touch the registry implementation (componentRegistry.register) to accept the generic ComponentType and coerce once to ComponentRenderer so callers (Card, List, Row, etc.) no longer bypass TypeScript checks.packages/genui/a2ui/src/catalog/Divider/style.css (1)
3-9: Nit:overflow: autoon a 1px divider looks unintended.A divider with
height: 1px(orwidth: 1px) has no content to scroll;overflow: autocan cause scrollbar artifacts on some renderers. Consideroverflow: hidden(or removing it entirely).Proposed tweak
.divider { display: block; min-height: 0; - overflow: auto; + overflow: hidden; background-color: var(--rule); border: none; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Divider/style.css` around lines 3 - 9, The .divider CSS uses overflow: auto which can produce scrollbar artifacts for a 1px divider; update the .divider rule (class name ".divider") to remove overflow: auto or replace it with overflow: hidden (or simply omit overflow entirely) so the 1px/1px divider doesn't trigger scrollbars and remains visually stable.packages/genui/a2ui/src/catalog/Image.ts (1)
8-8: Consider tighteningComponentRendererto avoidas unknown ascasts.The double-cast is repeated across every catalog registrar (e.g.,
Text.ts,Image.ts). IfComponentRendererwere typed to accept function components takingGenericComponentProps, these casts could be removed, giving better type safety at registration sites.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Image.ts` at line 8, The catalog registrations use unsafe double-casts like "Image as unknown as ComponentRenderer"; update the ComponentRenderer type definition so it accepts React function components that take GenericComponentProps (or a union that includes FunctionComponent<GenericComponentProps>) and any necessary generics, then remove the "as unknown as" casts at registration sites (e.g., componentRegistry.register('Image', Image) and similar in Text.ts). Locate and change the ComponentRenderer type declaration (and any central registry typing) to reference GenericComponentProps and re-run type checks to adjust callers if needed.packages/genui/a2ui/src/catalog/Text/index.tsx (2)
9-9: Optional: prefer a named type import over inlineimport('@lynx-js/react').ReactNode.A top-level
import type { ReactNode } from '@lynx-js/react'is more idiomatic and lets the return type be reused.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Text/index.tsx` at line 9, Replace the inline return type import with a top-level named type import: add a top-level line importing the ReactNode type (import type { ReactNode } from '@lynx-js/react') and update the component/function return annotation from import('@lynx-js/react').ReactNode to simply ReactNode in the Text component (or the exported function in this file) so the type can be reused and is more idiomatic.
10-17: Minor: unsafeas stringcast and misleadingkeyon a single root element.
props['text']is typed broadly; casting tostringvia{text as string}hides bugs when a non-string slips through (e.g.,undefinedor number from a server-driven payload). PreferString(text ?? '')or a runtime guard.key={id}on a non-list root has no effect; consider dropping it unless a parent expects it for reconciliation.Proposed tweak
- const id = props.id; - const text = props['text']; + const text = props['text']; const variant = props['variant'] as string | undefined ?? 'body'; return ( - <text key={id} className={`text-${variant}`}> - {text as string} + <text className={`text-${variant}`}> + {typeof text === 'string' ? text : String(text ?? '')} </text> );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Text/index.tsx` around lines 10 - 17, Replace the unsafe cast and the misleading key: remove key={id} from the root <text> element and change the content rendering from {text as string} to a safe runtime conversion such as {String(text ?? '')} (or validate typeof text === 'string' and fallback to ''), referencing the id, text and variant variables in this file (index.tsx) to locate the code to update.packages/genui/a2ui/src/utils/ComponentRegistry.ts (1)
4-18: Duplicate ofBaseComponentRegistry<T>incore/ComponentRegistry.ts.This class has the exact same shape (
Map<string, T>plusregister/get/has) asBaseComponentRegistry<T>already defined inpackages/genui/a2ui/src/core/ComponentRegistry.ts, and no catalog module imports the utils version — they all import fromcore/ComponentRegistry.js. Consider either deleting this file (and its export fromutils/index.ts) or re-exportingBaseComponentRegistryfrom core to keep a single source of truth.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/utils/ComponentRegistry.ts` around lines 4 - 18, This file defines ComponentRegistry<T> which is a duplicate of BaseComponentRegistry<T> in core/ComponentRegistry.ts; remove the duplication by deleting ComponentRegistry<T> and its export from utils/index.ts, or replace its export with a re-export of BaseComponentRegistry from core (i.e., export { BaseComponentRegistry as ComponentRegistry } from '.../core/ComponentRegistry') so there is a single source of truth; update any imports relying on ComponentRegistry to continue working via the core export if needed.packages/genui/a2ui/src/catalog/luna-styles/lunaris-dark.css (1)
19-19: Nit: inconsistent hex casing.
#362E46uses uppercase while every other hex value in this file is lowercase (#0d0d0d,#ff8ab5, etc.). Lowercase for consistency:- --secondary-2: `#362E46`; + --secondary-2: `#362e46`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/luna-styles/lunaris-dark.css` at line 19, The CSS variable --secondary-2 currently uses an uppercase hex literal; change its value from `#362E46` to lowercase `#362e46` so it matches the file's lowercase hex casing convention and maintains consistency with other variables like --primary, --background, etc.packages/genui/a2ui/src/catalog/Button.ts (1)
8-8: Double cast hides a real type mismatch.
Button as unknown as ComponentRendererbypasses TypeScript's type checker.ComponentRenderer = ComponentType<ComponentProps>, so ifButtonactually acceptsGenericComponentProps(or similar), prefer makingButton's prop type compatible withComponentProps(e.g., extend it) or widenComponentRendererto a broader functional component shape, rather than silently casting throughunknown. This same pattern appears in every catalog registration (Divider.ts, etc.); fixing the root types will eliminate the casts everywhere.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Button.ts` at line 8, The registration uses a double-cast (Button as unknown as ComponentRenderer) which hides a real props mismatch; update the Button component props to be assignable to ComponentProps (e.g., have ButtonProps extend ComponentProps or map GenericComponentProps into ComponentProps) or adjust the ComponentRenderer type to accept the broader functional component shape, then remove the unsafe cast in componentRegistry.register('Button', Button). Apply the same root-type fix for other catalog entries (e.g., Divider) so registrations can pass types without casting.packages/genui/a2ui/src/catalog/Image/style.css (1)
1-38: Generic, unscoped class names risk collisions.
.icon,.avatar,.header,.smallFeature,.mediumFeature,.largeFeatureare very common names and live in the global CSS namespace. Sincea2ui-imageis already used as the namespaced base class, consider either scoping the variants (e.g.,.a2ui-image.icon, or.a2ui-image--icon) or renaming them with thea2ui-image-prefix to avoid clashing with consumer styles or other catalog components.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Image/style.css` around lines 1 - 38, The CSS uses generic global class names (.icon, .avatar, .header, .smallFeature, .mediumFeature, .largeFeature) which can collide; update the stylesheet to scope or prefix these with the component base (.a2ui-image) — e.g., convert to scoped selectors like .a2ui-image.icon or BEM-style names like .a2ui-image--icon / .a2ui-image--avatar / .a2ui-image--header / .a2ui-image--smallFeature / .a2ui-image--mediumFeature / .a2ui-image--largeFeature and update any JSX/markup using those classes to the new names so styles remain local to the Image component.packages/genui/a2ui/src/catalog/Button/style.css (1)
8-11: Optional: collapse padding to shorthand.- padding-left: 12px; - padding-right: 12px; - padding-top: 8px; - padding-bottom: 8px; + padding: 8px 12px;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Button/style.css` around lines 8 - 11, Replace the four separate padding declarations in the CSS rule in Button/style.css (the block containing padding-left, padding-right, padding-top, padding-bottom) with a single shorthand padding property (e.g., padding: 8px 12px;) to collapse them into one concise declaration while preserving the same vertical and horizontal spacing.packages/genui/a2ui/src/catalog/Image/index.tsx (1)
21-23: Hardcoded external fallback image URL.The fallback src points to a specific bytednsdoc.com asset. This couples the component to an external CDN (availability, offline use, brand) and isn't configurable per-theme or per-consumer. Consider making the fallback configurable via props/registry or bundling a local asset.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Image/index.tsx` around lines 21 - 23, The component currently hardcodes an external CDN fallback for image rendering (finalSrc uses that bytednsdoc URL when hasError is true); change this to a configurable fallback by adding a fallbackSrc prop (or pulling a theme/registry value) with a sensible default (e.g., a bundled local asset imported into Image/index.tsx), then update the finalSrc logic to use fallbackSrc when hasError is true (fall back to url otherwise). Ensure the new prop is optional with a default value and document/update any callers to pass a custom fallback if needed.packages/genui/a2ui/src/catalog/Card/style.css (1)
21-40: Unused Card variants — wire them up via a prop or drop them.
.card-outlined,.card-filled, and.card-ghostare defined here butCard/index.tsxhardcodesclassName='card card-elevated'(seepackages/genui/a2ui/src/catalog/Card/index.tsx:25-27), so these three variants are currently dead CSS. Either expose avariantprop onCardand map it to the class, or remove the unused rules until needed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Card/style.css` around lines 21 - 40, Card has hardcoded className='card card-elevated' so the CSS rules .card-outlined, .card-filled and .card-ghost are unused; update the Card component (Card/index.tsx) to accept a variant prop (e.g. 'outlined'|'filled'|'ghost'|'elevated' with default 'elevated'), map that prop to the corresponding CSS class name (card-outlined, card-filled, card-ghost, or card-elevated), compose it with the base 'card' class and any incoming className prop, and forward props as before so the three CSS variants become reachable; alternatively, if you prefer not to add variants, remove those unused rules from style.css.packages/genui/a2ui/src/catalog/Divider/index.tsx (1)
6-13:DividerPropsandComponentPropsimport appear unused.
Divideris typed as(props: GenericComponentProps), so the locally declaredDividerPropsinterface and theComponentPropsimport on line 6 are not referenced. Either drop them or actually type the component withDividerPropsfor stronger typing ofaxis/component.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Divider/index.tsx` around lines 6 - 13, The file currently declares DividerProps and imports ComponentProps but the Divider implementation uses GenericComponentProps, leaving both unused; either remove the unused ComponentProps import and the local DividerProps interface, or (preferred) apply the stronger typing by changing the Divider component signature to use DividerProps (so the component param is typed as DividerProps or DividerProps & GenericComponentProps if other generic fields are needed), ensuring axis/component types are enforced and retaining the ComponentProps import; update any related references and remove any now-unused imports.packages/genui/a2ui/src/catalog/RadioGroup/style.css (1)
59-63:.labelis a very generic global class.Defining
.label { font-size: 16px; color: var(--content); … }inside a component-scoped stylesheet that gets imported globally is risky — any other component in the app rendering an element withclass="label"will inherit this typography. Consider scoping it (e.g..radio-option .labelor rename to.radio-option-label) to avoid cross-component bleed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/RadioGroup/style.css` around lines 59 - 63, The global .label class in style.css can leak styles to other components; scope or rename it to avoid collisions by changing the selector to a component-scoped name (for example .radio-option .label or rename to .radio-option-label) and update any corresponding markup that uses class="label" (e.g., radio option rendering in the RadioGroup/RadioOption component) so the CSS selector and HTML class names match; ensure the new selector preserves the existing declarations (font-size, color, line-height).packages/genui/a2ui/src/catalog/CheckBox/index.tsx (1)
11-18:CheckBoxPropsis declared but never applied.
CheckBoxPropsis exported but the component is typed withGenericComponentProps, so the more specific type provides no compile-time guarantees oncomponent/dataContextPath. Either drop the interface or use it as the function parameter type.Same shape (
Props extends ComponentProps { component: v0_9.AnyComponent & { dataContextPath?: string } }) is duplicated inRow/index.tsxandColumn/index.tsx; consider hoisting it into a shared types module.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/CheckBox/index.tsx` around lines 11 - 18, The export CheckBoxProps is unused because the CheckBox component is typed with GenericComponentProps; change the CheckBox function signature to accept props: CheckBoxProps instead of GenericComponentProps (so the component field and optional dataContextPath are type-checked), or if you prefer not to specialize here remove the unused CheckBoxProps export; additionally extract the repeated type shape (Props extends ComponentProps { component: v0_9.AnyComponent & { dataContextPath?: string } }) into a shared types module and import it into CheckBox, Row, and Column to avoid duplication.packages/genui/a2ui/src/catalog/Text/style.css (1)
60-74: Hard-coded price/link colors bypass the Luna theme.
#ff4d4fand#1890ffwon't switch withluna-dark(or any future theme). Since the rest of the file routes throughvar(--content*), these two should also map to theme tokens (e.g., a--accent/--linktoken added to the Luna palettes) so dark mode renders correctly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Text/style.css` around lines 60 - 74, Replace the hard-coded colors in the .text-price and .text-link rules with theme CSS variables so they follow Luna palettes and respect luna-dark; specifically, change color: `#ff4d4f` in .text-price to use a token like var(--accent, `#ff4d4f`) and change color: `#1890ff` in .text-link to use var(--link, `#1890ff`) (or the project’s existing --content-* naming if preferred), and ensure the Luna theme palettes define these --accent / --link (or --content-accent / --content-link) tokens for light and dark modes so the styles update with the theme.packages/genui/a2ui/src/catalog/Row/style.css (1)
9-49:alignment-*/distribution-*are duplicated withColumn/style.css.The same ten utility classes are defined in both
Row/style.cssandColumn/style.css. The rules are identical today, so behavior is fine, but any future tweak in one file will silently diverge from the other (and the import order will decide the winner). Move these shared utilities intoluna-styles/(or a dedicatedlayout.css) and import once.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/catalog/Row/style.css` around lines 9 - 49, The alignment-* (alignment-start, alignment-center, alignment-end, alignment-stretch) and distribution-* (distribution-start, distribution-center, distribution-end, distribution-spaceBetween, distribution-spaceAround, distribution-spaceEvenly) utility classes are duplicated in Row/style.css and Column/style.css; move these shared rules into a single shared stylesheet (e.g., luna-styles/layout.css or luna-styles/utilities.css), import that shared file once (from your global styles entry or the Row/Column components as appropriate), and remove the duplicated rules from Row/style.css and Column/style.css so the classes are defined in one place and consumed via import.packages/genui/a2ui/src/core/BaseClient.ts (2)
18-21:randomIdusesMath.random— collisions possible under load.Using
Date.now()+Math.random().slice(2,10)produces ~40 bits of entropy and is fine for local/dev use, but for production task IDs (which keythis.resources/this.resolves), considercrypto.randomUUID()where available, falling back to the current implementation. Collisions here would cause a later request to overwrite an earlier resource mapping.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/BaseClient.ts` around lines 18 - 21, randomId currently uses Date.now() + Math.random() which has limited entropy and can cause collisions that overwrite entries in this.resources / this.resolves; update the randomId function to prefer crypto.randomUUID() when available (browser or Node crypto) and fall back to the existing Date.now()+Math.random() construction if not, so callers like the BaseClient resource/resolves keys get a collision-resistant UUID without changing call sites.
372-381: ObfuscatedEventSourcelookup hurts readability.const tsKey = 'Event' + 'Source'; const NativeES = g[tsKey] as …;If this string concat is to evade a bundler/static analyzer, please leave a comment explaining why; otherwise use
globalThis.EventSourcedirectly — the split-string trick is surprising and looks like dead code to a reader.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/BaseClient.ts` around lines 372 - 381, The concatenated string lookup (tsKey) and indirection (NativeES) make the EventSource resolution in BaseClient.ts hard to read; replace the obfuscated lookup with a direct reference to globalThis.EventSource when building EventSourceImpl (i.e., remove tsKey/NativeES and use globalThis.EventSource or window.EventSource explicitly), or if the split-string trick is intentional to avoid bundler/static-analysis behavior, add a concise comment above tsKey explaining that intent and why it's required; update references to EventSourceImpl accordingly.packages/genui/a2ui/src/core/A2UIRender.tsx (1)
39-41: Avoid inlineimport('…')type casts.These
import('@lynx-js/react').ReactNodeuses inline-import the already-importedReactNodetype. You importedReactNodeon Line 5 — use it directly for readability and to avoid duplicating the type source.- const Component = componentRegistry.get(tag)! as unknown as ( - props: Record<string, unknown>, - ) => import('@lynx-js/react').ReactNode; + const Component = componentRegistry.get(tag)! as unknown as ( + props: Record<string, unknown>, + ) => ReactNode;Same for Lines 64, 66, 166.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/A2UIRender.tsx` around lines 39 - 41, The code uses inline import type casts like import('@lynx-js/react').ReactNode when typing components retrieved from componentRegistry (e.g., the Component variable in A2UIRender.tsx); replace those inline import('…') types with the already-imported ReactNode type (remove import('…').ReactNode and use ReactNode directly) and apply the same fix to the other occurrences referenced in this file (the similar casts around the component retrieval and the two other spots noted).packages/genui/a2ui/src/core/processor.ts (3)
119-119:JSON.parse(JSON.stringify(...))deep clone is fragile.This silently drops
undefined, functions,Dateobjects, and throws on cycles. For the bounded shape ofComponentInstance(JSON-serializable) it is probably fine today, but considerstructuredClone(original)for a safer/faster alternative on modern runtimes, or a small explicit clone helper that preserves the documentedComponentInstancefields.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/processor.ts` at line 119, Replace the fragile JSON.parse(JSON.stringify(original)) cloning in processor.ts (where cloned is created as a ComponentInstance from original) with a safer clone: use structuredClone(original) on modern runtimes, or introduce a small explicit deepCloneComponentInstance helper that copies documented ComponentInstance fields (preserving Dates, functions if needed, and handling cycles) and return that result assigned to cloned; ensure the new implementation still asserts/returns a ComponentInstance type and falls back to the explicit helper when structuredClone is unavailable.
253-303: Child-template detection is brittle and hand-rolled.The
anyInstance['children']shape check (Lines 269-280) inlines 12 lines of narrowing. Factor it into a smallisTemplateRef(value): value is { componentId: string; path: string }helper — it's used again inupdateDataModel(Line 369-371) and would make both call sites easier to audit.Also,
!Array.isArray(anyInstance['children'])combined withtypeof … === 'object' && … !== nullalready implies non-array object; the explicit!Array.isArraycheck is redundant oncetypeof … === 'object'narrows, but keeping it for clarity is fine.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/processor.ts` around lines 253 - 303, The child-template detection logic is duplicated and brittle inside the updateComponents block; extract the inline narrowing into a small type-guard helper isTemplateRef(value): value is { componentId: string; path: string } and use it from both the updateComponents loop (where you currently check anyInstance['children']) and the updateDataModel call site; implement the helper to safely check for non-null object shape and string-typed componentId and path, then replace the 12-line inline check with a call to isTemplateRef and preserve the same use of resolvePath, instance.__template assignment, and existing casting/typing around children and dataContextPath so behavior is unchanged.
456-456: Module-level mutable singleton.
export const processor: MessageProcessor = new MessageProcessor()means every consumer ofprocessor.tsshares state, which prevents multiple A2UI runtimes on the same page and complicates testing (state leaks across tests). Consider exposing a factory alongside the default singleton, or wiring the processor through React context so tests can substitute a fresh instance.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/processor.ts` at line 456, The module currently exports a shared singleton (export const processor: MessageProcessor = new MessageProcessor()) which leaks state; change the API to export a factory (e.g., export function createProcessor(): MessageProcessor) that returns new MessageProcessor() and keep an optional default instance (export const processor = createProcessor()) only for convenience; update consumers to accept a processor via props or React context (provide a ProcessorContext) so tests can inject createProcessor() instances and components can use the default singleton when wiring is not provided. Ensure MessageProcessor remains unchanged and update tests to call createProcessor() to get isolated instances.packages/genui/a2ui/src/core/useDataBinding.ts (1)
96-104: Redundantelse if / elsebranches.Both branches assign
result[key] = prop;— fold them into a single else, or drop the type-check branch entirely:- } else if ( - typeof prop === 'string' - || typeof prop === 'number' - || typeof prop === 'boolean' - ) { - result[key] = prop; - } else { - result[key] = prop; - } + } else { + result[key] = prop; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/useDataBinding.ts` around lines 96 - 104, The branches in useDataBinding.ts that check typeof prop and then assign result[key] = prop in both the else-if and else are redundant; simplify by removing the type-check branch and replace the entire conditional with a single assignment to result[key] = prop (or collapse to one else block) so only one code path assigns the value; update the block that references prop, result, and key accordingly.packages/genui/a2ui/src/core/types.ts (1)
32-33: Nit: redundant| nullwith optional marker.
rootComponentId?: string | nullmeansstring | null | undefined. Ifnullis semantically meaningful (processor.ts explicitly assignsnull), keep it; otherwise simplify torootComponentId?: string. Consumers currently mix both styles (A2UIRender.tsxline 141 uses!, treating it as definitely string).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/genui/a2ui/src/core/types.ts` around lines 32 - 33, rootComponentId is declared as `rootComponentId?: string | null` which yields string|null|undefined—either remove `| null` and make the type `rootComponentId?: string` or, if `null` is semantically required, keep `| null` and make consumers explicit; prefer removing `| null` here: change the type to `rootComponentId?: string` and update the producer `processor.ts` to assign undefined instead of null (or stop assigning null), and ensure consumers like A2UIRender.tsx no longer rely on mixed null semantics.
React MTF Example#549 Bundle Size — 193.94KiB (0%).8e89ff2(current) vs 3118b10 main#540(baseline) Bundle metrics
|
| Current #549 |
Baseline #540 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
173 |
173 |
|
66 |
66 |
|
43.94% |
43.94% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #549 |
Baseline #540 |
|
|---|---|---|
111.23KiB |
111.23KiB |
|
82.71KiB |
82.71KiB |
Bundle analysis report Branch PupilTong:p/hw/pick-gen-ui Project dashboard
Generated by RelativeCI Documentation Report issue
React Example#7416 Bundle Size — 223.33KiB (0%).8e89ff2(current) vs 3118b10 main#7407(baseline) Bundle metrics
|
| Current #7416 |
Baseline #7407 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
4 |
4 |
|
179 |
179 |
|
69 |
69 |
|
44.48% |
44.48% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #7416 |
Baseline #7407 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
77.58KiB |
77.58KiB |
Bundle analysis report Branch PupilTong:p/hw/pick-gen-ui Project dashboard
Generated by RelativeCI Documentation Report issue
React External#533 Bundle Size — 580.35KiB (0%).8e89ff2(current) vs 3118b10 main#524(baseline) Bundle metrics
|
| Current #533 |
Baseline #524 |
|
|---|---|---|
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 PupilTong:p/hw/pick-gen-ui Project dashboard
Generated by RelativeCI Documentation Report issue
Web Explorer#8990 Bundle Size — 898.09KiB (-0.03%).8e89ff2(current) vs 3118b10 main#8981(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch PupilTong:p/hw/pick-gen-ui Project dashboard Generated by RelativeCI Documentation Report issue |
Merging this PR will not alter performance
Comparing Footnotes
|
aa41dd4 to
16826b5
Compare
16826b5 to
0adea96
Compare
6079714 to
8e89ff2
Compare
Summary by CodeRabbit
New Features
Documentation
Style
Chores
Checklist