Skip to content

Input Agnostic Player and Native A2UI Support#866

Open
KetanReddy wants to merge 28 commits into
mainfrom
feture/a2ui
Open

Input Agnostic Player and Native A2UI Support#866
KetanReddy wants to merge 28 commits into
mainfrom
feture/a2ui

Conversation

@KetanReddy

@KetanReddy KetanReddy commented May 12, 2026

Copy link
Copy Markdown
Member

What and Why

With the emergence of A2UI and other agent-driven standards, Player — in its mission to serve the broader SDUI space — should expand support for more formats beyond its own in order to broaden the scope of what Player solves for. Furthermore, this lets capabilities be built against Player itself, regardless of input format, allowing for the decoupling of implementation from the server driving it.

This PR adds the generic "unknown format" entrypoint to every platform (web, JVM, Android, iOS) and ships A2UI as the first non-Player format end-to-end.

Core Player Changes

  • New transformContent waterfall hook on Player's hooks. Fires at the top of start(), after plugins are registered and before resolveFlowContent. Plugins inspect a ContentMeta { format, version } and either convert the payload or pass it through.
  • Player.start() signature widened from (payload: Flow) to (payload: unknown, options?: StartOptions). Default format is "player", which preserves existing behavior (a plain Flow flows through untouched). version is free-form, so a single format plugin can dispatch across versions.

Platform Entrypoints

The hook and signature widening live in the core JS bundle every platform loads; each platform entrypoint forwards the format/version through. Default "player" keeps all existing call sites untouched.

  • ReactReactPlayer.start() mirrors the same signature (react/player/src/player.tsx) and forwards options to the underlying Player.start().
  • JVMPlayer.start() gains start(flow: String, format: String, version: String? = null) (plus a matching URL overload). HeadlessPlayer implements it and forwards { format, version } to the JS player.start(payload, options) as a bridge-encoded Map.
  • AndroidAndroidPlayer.start(flow, format, version) delegates to the wrapped HeadlessPlayer.
  • iOS — new StartOptions { format, version } value type; HeadlessPlayer.start(flow:options:completion:) appends the options object to the JS start args ([String: Any] → JS object via JavaScriptCore). Threaded through SwiftUIPlayer.init / Context.load / ManagedPlayer via a defaulted startOptions: param (source-compatible). Use StartOptions.a2ui.

Plugins

Core: @player-ui/a2ui-plugin

Includes three sub-plugins:

  • A2UIContentPlugin: Only activates when meta.format === "a2ui". Calls adaptA2UIToFlow(snapshot) to transform content.
  • A2UITransformPlugin: Per-asset transforms (Button/TextField/CheckBox/Slider/DateTimeInput/ChoicePicker/Text) that attach run()/set()/value helpers consumed by the rendering layer.
  • A2UIExpressionsPlugin: Registers the A2UI v0.9.1 standard library: validation (required, regex, length, numeric, email), formatters (formatString, formatNumber, formatCurrency, formatDate, pluralize), openUrl, and logic (and/or/not).

This package emits a native JS bundle (A2UIPlugin.native.js), which the JVM and iOS wrappers below load directly — so the adapter, transforms, and expression std-lib are shared across all platforms with zero reimplementation.

Adapter Logic

Walks the flat components[] list from id: "root", inlines child references into a nested asset tree matching Player's {asset: ...} shape, and produces a Flow with a single VIEW state plus one END per unique event name encountered. Translation rules:

  • {path: "/x/y"}"x.y" (Player binding)
  • formatString(...)"Hello, {{x.y}}!" template
  • Other {call, args}@[fn(...)]@ expression
  • checks: [...] on inputs → lifted into a synthesized Flow.schema via synthesizeSchema so Player's existing SchemaController/ValidationController pick them up unchanged
  • Templated children: {path, componentId} blocks become indexed paths scoped to <scope>._index_
  • Cycle detection throws on non-tree component graphs

Platform Renders

The A2UI v0.9.1 reference catalog — 16 assets — implemented per platform: Row, Column, List, Text, Image, Icon, Divider, Card, Modal, Tabs, Button, TextField, CheckBox, Slider, DateTimeInput, ChoicePicker. Asset type strings are PascalCase to match the adapter output; transformed assets consume the shared run()/set()/currentValue helpers.

  • React: @player-ui/a2ui-plugin-react — the full catalog as React components.
  • Android: plugins/a2ui/androidA2UIPlugin : AndroidPlayerPlugin, JSPluginWrapper by <jvm wrapper> registering the catalog as Jetpack Compose renderers. Function helpers decode as Kotlin function types.
  • iOS: plugins/a2ui/swiftuiA2UIPlugin : JSBasePlugin, NativePlugin that loads the bundle and registers the catalog as SwiftUI renderers. Function helpers decode as Swift WrappedFunctions.
  • JVM: plugins/a2ui/jvm — generated Kotlin JSPluginWrapper (A2UIPlugin) that loads the A2UI JS bundle (mirrors reference-assets/jvm). Headless-capable, no UI layer.

Packages

A new folder in the repo! The goal for this folder is to ship preconfigured Player entrypoints so consumers don't have to assemble plugins themselves. The plugins/ directory exports building blocks; packages/ exports ready-to-use Players for specific content formats.

Each preset comes with the A2UI plugin pre-added and appends any consumer-supplied plugins after it. Since HeadlessPlayer/AndroidPlayer are final, the JVM/Android entries are idiomatic factory functions; iOS is a thin View wrapper that also defaults startOptions: .a2ui.

React: @player-ui/a2ui

import { A2UIReactPlayer } from "@player-ui/a2ui";

JVM: packages/a2ui/jvm

val player = A2UIHeadlessPlayer()
player.start(snapshot, "a2ui")

Android: packages/a2ui/android

val player = A2UIAndroidPlayer()
player.start(snapshot, "a2ui")

iOS: packages/a2ui/swiftui

A2UISwiftUIPlayer(flow: snapshot, result: $result)

Mocks

The canonical catalog is plugins/a2ui/mocks (21 snapshots). JS consumes it via @player-ui/a2ui-plugin-mocks; JVM/Android via //tools/mocks:jar. iOS mirrors the full set as faithful inline copies (the existing iOS demo/test convention, there is no runtime mechanism to read the canonical JSON on iOS today).

Storybook

New A2UI stories with one story per asset, driven by a createA2UIStory helper in the Player storybook extension that can render A2UI content.

Demo Apps

  • iOS — an "A2UI" screen renders the full canonical snapshot catalog, each started with .a2ui.
  • Android — the demo PlayerViewModel registers A2UIPlugin alongside the reference assets (disjoint type namespaces).

Notes

  • Android Image/Icon (no Coil / material-icons-core only) and iOS Image (deployment target iOS 14 predates AsyncImage) render labelled placeholders; iOS Icon uses SF Symbols.

To Align On

Overall Approach

  • Do we want to have this switch inside of Player or do we want to offer two exports from core, one per input format, to minimize bundle size

Change Type (required)

Indicate the type of change your pull request is:

  • patch
  • minor
  • major
  • N/A

Does your PR have any documentation updates?

  • Updated docs
  • No Update needed
  • Unable to update docs

Release Notes

TBD

📦 Published PR as canary version: 1.1.0--canary.866.38513

Try this version out locally by upgrading relevant packages to 1.1.0--canary.866.38513

@KetanReddy KetanReddy added the minor Increment the minor version when merged label May 12, 2026
@KetanReddy

Copy link
Copy Markdown
Member Author

/docs

@codecov

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 670.02kB (12.96%) ⬆️⚠️, exceeding the configured threshold of 5%.

Bundle name Size Change
tools/storybook 113.97kB 1.85kB (1.65%) ⬆️
plugins/check-path/core 443.17kB 563 bytes (0.13%) ⬆️
plugins/async-node/core 504.82kB 563 bytes (0.11%) ⬆️
plugins/markdown/core 683.84kB 574 bytes (0.08%) ⬆️
plugins/reference-assets/core 555.47kB 563 bytes (0.1%) ⬆️
plugins/beacon/core 424.76kB 563 bytes (0.13%) ⬆️
react/player 83.87kB 72 bytes (0.09%) ⬆️
plugins/metrics/core 461.33kB 563 bytes (0.12%) ⬆️
core/player 1.02MB 1.22kB (0.12%) ⬆️
plugins/external-state/core 427.43kB 563 bytes (0.13%) ⬆️
plugins/a2ui/react 88.36kB 88.36kB (100%) ⬆️⚠️
packages/a2ui/react 3.2kB 3.2kB (100%) ⬆️⚠️
plugins/a2ui/core 571.36kB 571.36kB (100%) ⬆️⚠️

Affected Assets, Files, and Routes:

view changes for bundle: plugins/check-path/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
CheckPathPlugin.native.js 563 bytes 413.68kB 0.14%
view changes for bundle: tools/storybook

Assets Changed:

Asset Name Size Change Total Size Change (%)
index.js 974 bytes 58.83kB 1.68%
index.mjs 879 bytes 55.15kB 1.62%
view changes for bundle: plugins/beacon/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
BeaconPlugin.native.js 563 bytes 410.3kB 0.14%
view changes for bundle: core/player

Assets Changed:

Asset Name Size Change Total Size Change (%)
Player.native.js 563 bytes 426.91kB 0.13%
cjs/index.cjs 230 bytes 202.51kB 0.11%
index.legacy-esm.js 212 bytes 195.76kB 0.11%
index.mjs 212 bytes 195.76kB 0.11%
view changes for bundle: react/player

Assets Changed:

Asset Name Size Change Total Size Change (%)
cjs/index.cjs 24 bytes 30.41kB 0.08%
index.legacy-esm.js 24 bytes 26.73kB 0.09%
index.mjs 24 bytes 26.73kB 0.09%
view changes for bundle: plugins/external-state/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
ExternalStatePlugin.native.js 563 bytes 408.46kB 0.14%
view changes for bundle: plugins/markdown/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
MarkdownPlugin.native.js 574 bytes 658.69kB 0.09%
view changes for bundle: plugins/metrics/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
MetricsPlugin.native.js 563 bytes 428.98kB 0.13%
view changes for bundle: plugins/async-node/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
AsyncNodePlugin.native.js 563 bytes 442.19kB 0.13%
view changes for bundle: plugins/reference-assets/core

Assets Changed:

Asset Name Size Change Total Size Change (%)
ReferenceAssetsPlugin.native.js 563 bytes 517.48kB 0.11%

intuit-svc added a commit to player-ui/player-ui.github.io that referenced this pull request May 12, 2026
@intuit-svc

Copy link
Copy Markdown
Contributor

Docs Preview

A preview of your PR docs was deployed by CircleCI #36492 on Tue, 12 May 2026 07:39:49 GMT

📖 Docs (View site)

@codecov

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (c96e810) to head (4ebf47a).
⚠️ Report is 18 commits behind head on main.

Additional details and impacted files
@@     Coverage Diff     @@
##   main   #866   +/-   ##
===========================
===========================

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@KetanReddy

Copy link
Copy Markdown
Member Author

/canary

intuit-svc added a commit to player-ui/player-ui.github.io that referenced this pull request May 12, 2026
@intuit-svc

intuit-svc commented May 12, 2026

Copy link
Copy Markdown
Contributor

Build Preview

Your PR was deployed by CircleCI #38513 on Thu, 02 Jul 2026 06:24:58 GMT with this version:

1.1.0--canary.866.38513

📖 Docs (View site)

@KetanReddy

Copy link
Copy Markdown
Member Author

/canary

intuit-svc added a commit to player-ui/player-ui.github.io that referenced this pull request May 22, 2026
@KetanReddy KetanReddy changed the title [WIP] Native A2UI Support Native A2UI Support May 22, 2026
@intuit-svc

intuit-svc commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Comparison against baseline from main. ⚠️ = regression (>10% slower), ✅ = improvement (>5% faster)

core/player ⚠️

Benchmark Current Baseline Change
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.bar 808.68K ops/s 792.61K ops/s +2.0%
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets.1.name 501.21K ops/s 447.84K ops/s +11.9% ✅
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets.01.name 463.04K ops/s 452.53K ops/s +2.3%
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets['01'].name 466.11K ops/s 461.73K ops/s +1.0%
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets[01].name 475.51K ops/s 464.71K ops/s +2.3%
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets[name = "frodo"].type 298.41K ops/s 214.51K ops/s +39.1% ✅
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["name" = "sprinkles"].type 231.57K ops/s 189.71K ops/s +22.1% ✅
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["isDog" = false].type 298.58K ops/s 271.18K ops/s +10.1% ✅
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["isDog" = true].type 308.96K ops/s 228.34K ops/s +35.3% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.bar 607.63K ops/s 484.95K ops/s +25.3% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets.1.name 400.50K ops/s 259.12K ops/s +54.6% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets.01.name 381.75K ops/s 279.20K ops/s +36.7% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets['01'].name 356.32K ops/s 285.38K ops/s +24.9% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets[01].name 397.29K ops/s 363.01K ops/s +9.4% ✅
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets[name = "frodo"].type 248.71K ops/s 243.94K ops/s +2.0%
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["name" = "sprinkles"].type 191.76K ops/s 200.91K ops/s -4.6%
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["isDog" = false].type 241.26K ops/s 237.34K ops/s +1.7%
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["isDog" = true].type 246.07K ops/s 230.38K ops/s +6.8% ✅
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = 1 + 3 (sync) 363.84K ops/s 431.98K ops/s -15.8% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = 1 + 3 (async) 275.02K ops/s 256.47K ops/s +7.2% ✅
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: conditional(true, true, false) (sync) 466.41K ops/s 446.50K ops/s +4.5%
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: conditional(true, true, false) (async) 317.30K ops/s 411.46K ops/s -22.9% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional({{bar}} > 0, true, false) (sync) 201.34K ops/s 187.69K ops/s +7.3% ✅
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional({{bar}} > 0, true, false) (async) 168.65K ops/s 176.75K ops/s -4.6%
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(conditional(true = false, false, true), conditional(false = false, true, false), conditional(true = true, false, true)) (sync) 153.01K ops/s 153.94K ops/s -0.6%
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(conditional(true = false, false, true), conditional(false = false, true, false), conditional(true = true, false, true)) (async) 125.31K ops/s 146.44K ops/s -14.4% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = await(asyncTestFunction(1)) (sync) N/A N/A N/A
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = await(asyncTestFunction(1)) (async) 219.16K ops/s 274.66K ops/s -20.2% ⚠️
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = asyncTestFunction(1) (sync) 329.59K ops/s 304.58K ops/s +8.2% ✅
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = asyncTestFunction(1) (async) 289.15K ops/s 250.35K ops/s +15.5% ✅
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: asyncTestFunction(1) (sync) 865.46K ops/s 802.33K ops/s +7.9% ✅
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: asyncTestFunction(1) (async) 678.36K ops/s 571.23K ops/s +18.8% ✅
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(!{{bar}} == false, await(asyncTestFunction(1)), false) (sync) 183.32K ops/s 155.77K ops/s +17.7% ✅
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(!{{bar}} == false, await(asyncTestFunction(1)), false) (async) 167.13K ops/s 128.38K ops/s +30.2% ✅
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > initial resolve 449.36 ops/s 611.80 ops/s -26.6% ⚠️
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > Resolving from cache 16.59K ops/s 18.52K ops/s -10.4% ⚠️
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > data changes 2.56K ops/s 2.99K ops/s -14.6% ⚠️
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > data changes slow 578.83 ops/s 635.20 ops/s -8.9%

plugins/async-node/core ⚠️

Benchmark Current Baseline Change
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 1 times 10.85K ops/s 14.01K ops/s -22.5% ⚠️
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 5 times 11.17K ops/s 12.49K ops/s -10.6% ⚠️
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 10 times 9.60K ops/s 10.49K ops/s -8.4%
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 50 times 3.16K ops/s 3.25K ops/s -2.9%
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 100 times 1.68K ops/s 1.78K ops/s -5.8%
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 1 times 5.93K ops/s 5.91K ops/s +0.3%
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 5 times 7.54K ops/s 7.91K ops/s -4.8%
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 10 times 6.24K ops/s 6.92K ops/s -9.8%
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 50 times 2.26K ops/s 2.60K ops/s -13.0% ⚠️
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 100 times 1.47K ops/s 1.50K ops/s -1.8%

react/player

Benchmark Current Baseline Change
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 1 ReactAssets 590.10 ops/s 532.19 ops/s +10.9% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 1 ReactAssets 731.35 ops/s 742.64 ops/s -1.5%
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 5 ReactAssets 583.61 ops/s 529.03 ops/s +10.3% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 5 ReactAssets 791.14 ops/s 787.69 ops/s +0.4%
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 10 ReactAssets 548.97 ops/s 530.01 ops/s +3.6%
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 10 ReactAssets 730.25 ops/s 701.94 ops/s +4.0%
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 50 ReactAssets 431.28 ops/s 426.81 ops/s +1.0%
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 50 ReactAssets 255.13 ops/s 237.77 ops/s +7.3% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 100 ReactAssets 386.65 ops/s 329.70 ops/s +17.3% ✅
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 100 ReactAssets 75.77 ops/s 78.58 ops/s -3.6%

@KetanReddy KetanReddy changed the title Native A2UI Support Input Agnostic Player and Native A2UI Support Jun 16, 2026
@KetanReddy KetanReddy marked this pull request as ready for review June 25, 2026 18:25
@KetanReddy KetanReddy requested review from a team as code owners June 25, 2026 18:25

@KVSRoyal KVSRoyal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some small questions. But otherwise this looks good from an ios perspective.

Comment thread MODULE.bazel
npm_package_target_name = "{dirname}",
npmrc = "//:.npmrc",
pnpm_lock = "//:pnpm-lock.yaml",
verify_node_modules_ignored = "//:.bazelignore",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does removing verify_node_modules_ignored do?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So before, we had to manually specify every node_module folder for Bazel to ignore to prevent loops in the dependency tree. This PR migrates away from that pattern to specify to just ignore all node_modules in the REPO.bazel flle. That way when we make a new JS based package we don't have to add it to that list.

Comment on lines +3 to +6
#if SWIFT_PACKAGE
import PlayerUI
import PlayerUISwiftUI
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can drop the conditional wrapper nowadays since we only release through Swift packages now. Can you make that change? Same for the other files like this.

// Just this now
import SwiftUI
import PlayerUI
import PlayerUISwiftUI

Comment on lines +59 to +69
#if SWIFT_PACKAGE
ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module)
#else
ResourceUtilities.urlForFile(
name: fileName,
ext: "js",
bundle: Bundle(for: A2UIPlugin.self),
pathComponent: "PlayerUI_A2UI.bundle"
)
#endif
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. We can just keep the if part now:

override open func getUrlForFile(fileName: String) -> URL? {
    ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module)
}

@KetanReddy

Copy link
Copy Markdown
Member Author

/canary

intuit-svc added a commit to player-ui/player-ui.github.io that referenced this pull request Jul 1, 2026
@KetanReddy

Copy link
Copy Markdown
Member Author

/canary

intuit-svc added a commit to player-ui/player-ui.github.io that referenced this pull request Jul 2, 2026
super({
...options,
plugins: [
new A2UIPlugin() as unknown as ReactPlayerPlugin,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type cast shouldn't be needed afaik. If there is an issue where it's required here I think we should try to tackle that. All PlayerPlugins should be acceptable here.

Comment on lines +22 to +23
(player as unknown as { logger: Parameters<typeof adaptA2UIToFlow>[1] })
.logger,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just use player.logger here?

Comment on lines +21 to +23
const snap =
(mod as { default?: A2UISnapshot }).default ?? (mod as A2UISnapshot);
return { default: snap as unknown as Record<string, unknown> };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const snap =
(mod as { default?: A2UISnapshot }).default ?? (mod as A2UISnapshot);
return { default: snap as unknown as Record<string, unknown> };
const snap = "default" in mod ? mod.default : mod;
return { default: snap };

This change will introduce a type issue because snap as an A2UISnapshot doesn't match the Mock type, but I think if mocks can be anything at this point we may just want to make that type object, unknown, or Record<string, unknown>.

@@ -0,0 +1,31 @@
import React from "react";
import type { A2UISnapshot } from "@player-ui/a2ui-plugin";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dependency has to be added to the package.json or BUILD for this package.

);

wp.start(jsonEditorValue.value)
wp.start(jsonEditorValue.value as never, format ? { format } : undefined)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
wp.start(jsonEditorValue.value as never, format ? { format } : undefined)
wp.start(jsonEditorValue.value, format ? { format } : undefined)

there are a few as never in adapter.ts as well that need to be removed

Comment thread core/player/src/player.ts
format: options?.format ?? "player",
version: options?.version,
};
const flow = this.hooks.transformContent.call(payload, meta) as Flow;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, the reason for transforming the content at this level is to ensure we have all the properties that Flow has (namely things like id and navigation) but is this appropriate for all parts of the object?

One consideration I have here is that support for different formats for something like the views should happen at the parser level, since all that really matters for the view/assets is the AST that gets produced. This is also something we need to think about with how this will interact with the AsyncNodePlugin since the A2UI format supports dynamic content updates. We need to parse new content into an AST but if we have to follow the same step of A2UI > Flow > AST it feels like we'll just end up with extra processing when we could just go straight to the AST.

Comment thread core/player/src/types.ts
Comment on lines +87 to +90
transformContent: SyncWaterfallHook<
[unknown, ContentMeta],
Record<string, any>
>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a Bail hook here is more appropriate than a Waterfall hook as it would let us require the output type to be a Flow. We could then tap in ourselves and for player format we can just return the object as it is. This would let us remove the type-casting from the Player start function which imo is error-prone.

and needs no conversion. Any other `format` is recognized by a plugin tapping the
core `transformContent` hook (e.g. the A2UI plugin claims `"a2ui"`).
*/
public struct StartOptions {

@JunDangIntuit JunDangIntuit Jul 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make StartOptions conform to Equatable and Sendable? Equatable allows us to write sth like ==, and Sendable for the thread safety

(defaults to `nil`, i.e. Player `Flow`s). Pass e.g. `.a2ui` for an A2UI experience.
- onError: A handler for when the `SwiftUIPlayer` encounters an error
- loading: A closure providing a `View` to display while the `FlowManager` fetches flows
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May we use /// instead of /** */ for iOS doc purpose. had a discussion previously and made an agreement to follow this guide https://google.github.io/swift/

Image

- startOptions: Describes the content `format`/`version` for every flow in the session
- onError: A handler for when the `SwiftUIPlayer` encounters an error
- loading: A closure providing a `View` to display while the `FlowManager` fetches flows
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here to replace /** */ with ///

- onError: A handler for when the `SwiftUIPlayer` encounters an error
- loading: A closure providing a `View` to display while the `FlowManager` fetches flows
*/
public init(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be public since ManagedPlayer14 is internal. It was made previously though.

- plugins: The plugins to use for the `SwiftUIPlayer`
- handleScroll: Whether or not the `ManagedPlayer` should wrap content in a `ScrollView`
- startOptions: Describes the content `format`/`version` for every flow in the session
- onError: A handler for when the `SwiftUIPlayer` encounters an error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not from this pr, but seems there is no onError parameter in the init

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Increment the minor version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants