From 3a7cb306e377555de34370de93765ba3c0c5c950 Mon Sep 17 00:00:00 2001 From: Jose Montes Date: Thu, 1 Oct 2026 20:06:34 +0000 Subject: [PATCH] fix(web_core): keep Record props as data in ResolveA2uiProp --- typescript/web_core/CHANGELOG.md | 2 +- typescript/web_core/src/resolution/generic-binder.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/typescript/web_core/CHANGELOG.md b/typescript/web_core/CHANGELOG.md index 6aa9cbda5f..b4eb59560f 100644 --- a/typescript/web_core/CHANGELOG.md +++ b/typescript/web_core/CHANGELOG.md @@ -1,7 +1,7 @@ ## Unreleased +- Fix `ResolveA2uiProp` resolving props declared as `Record` (for example `z.record(z.unknown())`) to action thunks instead of their data type: every object type is assignable to the `{functionCall?: any}` member of `ActionLike`, so object types with a string index signature are now treated as data. [#2939](https://github.com/a2ui-project/a2ui/pull/2939) - Add `ComponentNode.id`, a string unique across the document and stable for the node's lifetime, for renderers and components to use as a rendering key or as the base of DOM `id`s and accessibility references. [#2887](https://github.com/a2ui-project/a2ui/pull/2887) - - Universal elements accept an optional `node` (`A2uiWebComponentElement.node`): `A2uiLitElement` takes its `context` from it and passes each child its own node, and `renderA2uiNode` accepts a `ComponentNode`. `context` remains supported, so parents can move to nodes one at a time. [#2880](https://github.com/a2ui-project/a2ui/pull/2880) - Expose `ComponentNode.context`, the context the resolver bound the node with (undefined on placeholders). [#2879](https://github.com/a2ui-project/a2ui/pull/2879) diff --git a/typescript/web_core/src/resolution/generic-binder.ts b/typescript/web_core/src/resolution/generic-binder.ts index fc5c5aefd5..9f13cb40c9 100644 --- a/typescript/web_core/src/resolution/generic-binder.ts +++ b/typescript/web_core/src/resolution/generic-binder.ts @@ -297,6 +297,14 @@ type ActionLike = | {event: {name: string; context?: Record}} | {functionCall: {call: string; catalogId?: string; args?: Record}}; +/** + * Evaluates to true for object types with a string index signature, such as + * `Record`. Every object type is assignable to the weak + * `{functionCall?: any}` member of `ActionLike`, so these are checked + * separately: they carry data, not an action. + */ +type HasStringIndex = string extends keyof T ? true : false; + /** Evaluates to true if type T can contain a dynamic binding. */ type IsDynamic = DataBinding extends NonNullable ? true : false; @@ -316,7 +324,9 @@ export interface ResolvedChildRef { * For example, an `Action` object becomes a callable `() => Promise` function. */ export type ResolveA2uiProp = [NonNullable] extends [ActionLike] - ? (() => Promise) | Extract + ? HasStringIndex> extends true + ? Exclude + : (() => Promise) | Extract : [NonNullable] extends [ChildList] ? (string | ResolvedChildRef)[] | Extract : Exclude extends never