Skip to content

[v1.0][core] Implement metadata accessor and change notification APIs (getMetadata, setMetadata, onMetadataChanged) #3708

Closed

Description

@gspencergoog

Summary

The v1.0 protocol specification defines an SDK accessor pattern for surface and component metadata (metadata.extensions) in specification/v1_0/docs/a2ui_protocol.md (introduced in PR #2187):

  1. getMetadata() retrieves the metadata object containing extensions.
  2. setMetadata(metadata) updates the metadata object and notifies listeners.
  3. onMetadataChanged(callback) registers a callback that fires whenever metadata updates.

None of the core SDKs (TypeScript, Dart, Python, or Swift) or blueprints (a2ui_core.blueprint.md, node_resolution.blueprint.md) implement these metadata APIs.

Current state across SDKs

  1. In TypeScript (@a2ui/web_core), SurfaceModel.metadata is a read-only field initialized at createSurface, and ComponentModel.metadata is a plain mutable field. MessageProcessor.applyComponentUpdate assigns existing.metadata before existing.properties, which fires ComponentModel.onUpdated. Direct mutations to componentModel.metadata do not notify listeners, and ComponentNode does not expose or observe metadata (see [v1.0][web_core][react] ComponentNode and useNodeView omit component metadata.extensions from resolved nodes and toJSON() #3469).
  2. In Dart (a2ui_core), SurfaceModel.metadata is final, and ComponentModel.metadata is a plain mutable field updated before ComponentModel.onUpdated in MessageProcessor._applyComponentModels. Neither SurfaceModel, ComponentModel, nor ComponentNode provides getMetadata(), setMetadata(), or onMetadataChanged.
  3. In Python (a2ui_core), neither SurfaceModel nor ComponentModel extracts metadata onto a dedicated field or provides metadata accessors and change callbacks. MessageProcessor._process_update_components_op leaves "metadata" inside ComponentModel.properties.
  4. In Swift (A2UICore), SurfaceViewModel.metadata is a let constant, while ComponentModel has no metadata field. MessageProcessor.applyComponentsBatch leaves "metadata" inside properties.

Expected behavior

  1. Define the canonical metadata accessor and change notification contract (getMetadata(), setMetadata(metadata), and onMetadataChanged(callback), or idiomatic reactive equivalents) in a2ui_core.blueprint.md and node_resolution.blueprint.md.
  2. Implement the metadata APIs across the TypeScript, Dart, Python, and Swift core SDKs for surface and component extension metadata.

Activity

  1. added
    component: specificationAffects the A2UI specification documents and JSON schemas
    lang: swiftAffects SDKs or apps in the Swift programming language
    component: core sdkAffects the core SDK layer in any language
    lang: dartAffects SDKs or apps in the Dart programming language
    lang: pythonAffects SDKs or apps in the Python programming language
    on Oct 28, 2026
  2. added
    P1This is a priority. Team is actively working on it, or will be soon.
    on Oct 28, 2026
  3. gspencergoog commented on Oct 28, 2026

    @gspencergoog
    CollaboratorAuthor
  4. added
    status: needs-triageauto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs
    on Oct 28, 2026
  5. gspencergoog commented on Oct 28, 2026

    @gspencergoog
    CollaboratorAuthor

    Proposed API Design

    This comment outlines the target design for the v1.0 metadata APIs across all SDKs and blueprints.

    1. State Layer (SurfaceModel & ComponentModel)

    Both SurfaceModel and ComponentModel implement a unified MetadataHolder contract:

    • getMetadata() returns an immutable snapshot of the current A2uiMetadata ({"extensions": {...}}) or undefined / None / nil.
    • setMetadata(metadata) validates UAX 31 extension namespace keys and deep-copies the input so external mutations cannot change committed state. It updates the stored metadata and emits onMetadataChanged only when the value changes by structural equality.
    • onMetadataChanged is an EventSource<A2uiMetadata | undefined> (or Stream in Dart) that fires whenever setMetadata or an inbound updateComponents message changes the metadata.
    • During MessageProcessor updates (updateComponents), componentModel.setMetadata(newMetadata) runs independently of componentModel.properties. Metadata-only updates fire onMetadataChanged without emitting spurious onUpdated events when component properties are unchanged.

    2. Resolution & Catalog Layer (ComponentNode & ComponentApi)

    Framework adapter views only receive ComponentNode and SurfaceModel (and do not access ComponentModel directly). To allow views to recover and react to metadata without polluting ComponentNode.props:

    • ComponentNode exposes a reactive readonly metadata: Signal<A2uiMetadata | undefined> (or ValueListenable in Dart, @Observable property in Swift) plus a getMetadata() convenience getter.
    • NodeResolver seeds node.metadata from componentModel.getMetadata() on node creation and subscribes to componentModel.onMetadataChanged to update node.metadata in place without re-evaluating node.props.
    • ComponentApi exposes readonly metadata?: A2uiMetadata across TypeScript, Dart, Python, and Swift so views can recover static catalog definition metadata via node.impl?.metadata.

    3. Outbound Action Metadata (Issue #3468)

    • SurfaceModel.dispatchAction(payload, sourceComponentId, metadata?) and NodeAction(metadata?) accept optional renderer-side A2uiMetadata and include it on the outbound A2uiRendererAction payload (action.metadata.extensions).

    4. Agent-Side Authoring (Builders & Express DSL)

    • ComponentBuilderNode, CreateSurface, ComponentApi, and A2uiRendererAction accept metadata (or extensions shorthand) at construction time. They also provide .set_metadata(extensions) and .with_extension(namespace, value) helpers that validate UAX 31 extension keys immediately.
    • The v1.0 Express compiler and decompiler support metadata= and extensions= as reserved envelope keyword arguments on surface(...) and component calls. If a catalog component explicitly declares a schema property named metadata, the compiler routes metadata= to that property and reserves extensions= for the envelope.

    🤖 Agent-generated

  6. moved this from Todo to In Progress in A2UIon Oct 29, 2026
  7. added 2 commits that reference this issue on Oct 29, 2026
    472538a
    ec78bff
  8. moved this from In Progress to Done in A2UIon Oct 30, 2026
  9. added a commit that references this issue on Oct 30, 2026
    9055207
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1This is a priority. Team is actively working on it, or will be soon.component: core sdkAffects the core SDK layer in any languagecomponent: specificationAffects the A2UI specification documents and JSON schemaslang: dartAffects SDKs or apps in the Dart programming languagelang: pythonAffects SDKs or apps in the Python programming languagelang: swiftAffects SDKs or apps in the Swift programming languagelang: typescriptTypescript agent SDK issuesstatus: needs-triageauto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjstype: feature/enhancement

    Type

    No type

    Projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions