feat(swift): add SwiftUI rendering layer with Surface, CatalogView, and DataBinding bridge - #2093
feat(swift): add SwiftUI rendering layer with Surface, CatalogView, and DataBinding bridge#2093pinieb wants to merge 59 commits into
Conversation
…ma dependency Replaces the custom JSONSchema target with a multi-target Swift package structure that uses the swift-json-schema fork (v0.13.1) as an external dependency. This is the foundation for porting the A2UI v0.9.1 protocol implementation from prototype to production quality. Changes: - Remove custom JSONSchema target (JSONValue.swift + tests) - Add swift-json-schema dependency (from: "0.13.1") to Package.swift - Bump swift-tools-version to 6.1 and add macOS 13 to platforms - Define 4 production targets: A2UIJSON, A2UICore, A2UISwiftUI, A2UISampleClient - Define 3 test targets with placeholder tests - Create stub source files for each target - Update AGENTS.md to document new target architecture - Update .gitignore for Swift Package Manager artifacts Verification: - `swift package resolve` resolves swift-json-schema at 0.13.1 - `swift build` compiles all 7 targets - `swift test --enable-swift-testing` passes (3 placeholder tests)
…chema Implements all 14 A2UI v0.9.1 common type JSON Schema definitions using the swift-json-schema fork. Schemas are defined as a raw JSON document matching the specification's common_types.json exactly, and registered into Context via the remoteSchema parameter for $ref resolution. Changes: - Define complete common types document in A2UICommonSchema (all 14 defs: ComponentId, AccessibilityAttributes, ComponentCommon, ChildList, DataBinding, DynamicValue, DynamicString, DynamicNumber, DynamicBoolean, DynamicStringList, FunctionCall, CheckRule, Checkable, Action) - Add A2UISchemaRegistry helper to create a Context pre-populated with A2UI schemas - Add 31 tests covering URI helpers, document structure, schema registry, validation of all schema types (positive and negative cases), and cross-reference resolution (including recursive Action → FunctionCall → DynamicValue chains) - Remove placeholder A2UIJSONTests (superseded by comprehensive tests) Design notes: - Raw JSON string approach used instead of JSONValue dictionary literals to avoid Swift type-checker issues with deeply nested expressions - FunctionCall's catalog.json oneOf reference omitted; catalog-level validation handled at runtime by ComponentCatalog - Context.remoteSchemaStorage is internal in the fork, so schemas are registered via the Context(remoteSchema:) initializer Verification: - swift build passes (all targets) - swift test passes (34 tests in 3 suites, 0.017s)
… message models Ports the foundational A2UICore types from the prototype: error types, JSONValue path-based subscripting, and all server/client message models. Changes: - Add 4 error types: ClientServerError, GenericError, ValidationFailedError, LocalFunctionError (now Sendable) - Add JSONValue+Path extensions with type-safe accessors and JSON Pointer get/set subscripting, adapted for the fork's OrderedDictionary-based JSONValue (including .integer case handling) - Add Resolved protocol and ResolvedAction model with identity-based equality - Add 6 message types: EnvelopeMessage, CreateSurfaceMessage, DeleteSurfaceMessage, UpdateComponentsMessage, UpdateDataModelMessage, ClientToServerMessage (with v0.9/v0.9.1 version validation) - Add OrderedJSON and JSONSchema product dependencies to A2UICore target - Add 50 tests covering type accessors, path operations, message decoding/encoding round-trips, and error cases - Remove placeholder A2UICore.swift stub and placeholder test Verification: - swift build passes (all targets) - swift test passes (84 tests in 5 suites)
Ports the component catalog system and resolved model types that SurfaceViewModel will consume in a future PR. Changes: - Add catalog protocols: LocalFunction, ComponentCatalog (schema lookup, theme parsing, function registration), ActionHandling - Add resolved models: Node (immutable thread-safe component node with children accessor), DataBinding (generic two-way binding with path/literal identity), ComponentProperties (validated unresolved properties), SurfaceTheme (marker protocol) - Add Resolved conformances for primitives (String, Double, Int, Bool, JSONValue) and Array where Element == Node - Adapt ComponentCatalog and ComponentProperties to use fork's Schema type instead of prototype's JSONSchema - Add 33 tests with concrete test implementations (TestCatalog, ConcatFunction, IsEmptyFunction, TestTheme) covering schema lookup, theme parsing, function evaluation, node equality, data binding get/set, and component properties Verification: - swift build passes (all targets) - swift test passes (117 tests in 10 suites)
…e resolution Ports the SurfaceViewModel — the central runtime engine that manages component buffers, data model state, and resolved node tree building. Changes: - Add SurfaceViewModel: ObservableObject with thread-safe NSRecursiveLock-based access, @published rootNode, component buffer (raw JSONValue), data model with path-based updates, and theme state - Implement classifySchema by inspecting Schema.jsonValue as raw JSON to identify A2UI property types (DynamicBoolean, DynamicString, DynamicNumber, DynamicValue, Action, ChildList) via $ref, oneOf, anyOf, and allOf traversal - Implement resolveNode for recursive Node tree building with per-property classification and resolution - Implement evaluateDynamicValue for resolving literals, DataBinding path lookups, and FunctionCall invocations - Implement type-specific DataBinding resolvers (Bool, String, Double, JSONValue) with get/set closures and auto-rebuild - Implement resolveAction for server events and client function calls with deferred context resolution - Implement resolveChildList for static arrays and dynamic templates with data model iteration - Add 16 tests with concrete TestSurfaceCatalog, TestActionHandler, and TestConcatFunction covering component updates, data model updates, dynamic value resolution, action resolution, child lists, and themes Verification: - swift build passes (all targets) - swift test passes (133 tests in 11 suites)
…e resolution Ports the SurfaceViewModel — the central runtime engine that manages component buffers, data model state, and resolved node tree building. Changes: - Add SurfaceViewModel: ObservableObject with thread-safe NSRecursiveLock-based access, @published rootNode, component buffer (raw JSONValue), data model with path-based updates, and theme state - Implement classifySchema by inspecting Schema.jsonValue as raw JSON to identify A2UI property types (DynamicBoolean, DynamicString, DynamicNumber, DynamicValue, Action, ChildList) via $ref, oneOf, anyOf, and allOf traversal - Implement resolveNode for recursive Node tree building with per-property classification and resolution - Implement evaluateDynamicValue for resolving literals, DataBinding path lookups, and FunctionCall invocations - Implement type-specific DataBinding resolvers (Bool, String, Double, JSONValue) with get/set closures and auto-rebuild - Implement resolveAction for server events and client function calls with deferred context resolution - Implement resolveChildList for static arrays and dynamic templates with data model iteration - Add 16 tests with concrete TestSurfaceCatalog, TestActionHandler, and TestConcatFunction covering component updates, data model updates, dynamic value resolution, action resolution, child lists, and themes Verification: - swift build passes (all targets) - swift test passes (133 tests in 11 suites)
…nd DataBinding bridge Ports the SwiftUI rendering layer that connects A2UICore's resolved component tree to SwiftUI views. Changes: - Add CatalogView protocol for catalog-defined views constructed from resolved Node instances - Add A2UIThemeKey environment key for propagating SurfaceTheme through the SwiftUI view hierarchy - Add DataBinding+SwiftUI extension providing a single swiftUIBinding property that bridges A2UI's DataBinding to SwiftUI's Binding - Add Surface view — the root SwiftUI view for a single A2UI surface that observes SurfaceViewModel and renders via the catalog's CatalogView, with theme propagated through the environment - Remove placeholder A2UISwiftUI.swift stub - Add 9 tests covering Surface initialization and equality, DataBinding swiftUIBinding get/set, theme environment key defaults and mutation, and CatalogView initialization Verification: - swift build passes (all targets) - swift test passes (160 tests in 16 suites)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Pete Biencourt <piebie@google.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Pete Biencourt <piebie@google.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Pete Biencourt <piebie@google.com>
There was a problem hiding this comment.
Code Review
This pull request introduces several core components for A2UI, including SurfaceGroupModel for managing active surfaces, MessageParser and MessageErrorMapper for parsing and mapping errors, and a SwiftUI rendering layer with Surface and DataBinding extensions. Feedback on these changes highlights a critical bug in Surface's Equatable conformance that prevents SwiftUI from re-rendering on view model updates. Additionally, there are error-routing bugs in MessageProcessor where validation errors are not sent to the actionHandler or are silently ignored, and thread-safety concerns regarding background thread mutations of ObservableObjects, which can be resolved by adopting @MainActor.
|
|
||
| public init(viewModel: SurfaceViewModel, catalogType: Catalog.Type) { | ||
| self.viewModel = viewModel | ||
| self.catalogType = catalogType |
There was a problem hiding this comment.
What is CatalogType here?
There was a problem hiding this comment.
It was meant to be a way to give the CatalogView's type to the Surface since Swift isn't always nice about letting you do something like Surface<CatalogType>. CatalogType is basically a collection of ComponentImplementation types that plays nicely with SwiftUI.
Looking at the tests, maybe the specialized generic syntax will work though, so I might be able to streamline this initializer's API surface a little bit.
There was a problem hiding this comment.
It would be great to avoid this if possible! Note that in #2079, we are allowing multiple catalogs to be used together in the same surface in v1.0, so we'll need to update the code to support that eventually. This parameter might make that more difficult.
|
|
||
| public init(viewModel: SurfaceViewModel, catalogType: Catalog.Type) { | ||
| self.viewModel = viewModel | ||
| self.catalogType = catalogType |
There was a problem hiding this comment.
It would be great to avoid this if possible! Note that in #2079, we are allowing multiple catalogs to be used together in the same surface in v1.0, so we'll need to update the code to support that eventually. This parameter might make that more difficult.
Ports the SwiftUI rendering layer that connects A2UICore's resolved
component tree to SwiftUI views.
Changes:
resolved Node instances
the SwiftUI view hierarchy
property that bridges A2UI's DataBinding to SwiftUI's Binding
that observes SurfaceViewModel and renders via the catalog's
CatalogView, with theme propagated through the environment
DataBinding swiftUIBinding get/set, theme environment key defaults
and mutation, and CatalogView initialization
Verification: