Add ContextPlugin: shared Player state for external consumers#891
Draft
sugarmanz wants to merge 3 commits into
Draft
Add ContextPlugin: shared Player state for external consumers#891sugarmanz wants to merge 3 commits into
sugarmanz wants to merge 3 commits into
Conversation
Bundle ReportChanges will increase total bundle size by 115.23kB (2.05%) ⬆️. This is within the configured threshold ✅ Detailed changes
|
Member
Author
|
/canary |
intuit-svc
added a commit
to player-ui/player-ui.github.io
that referenced
this pull request
Jun 29, 2026
Member
Author
|
/canary |
intuit-svc
added a commit
to player-ui/player-ui.github.io
that referenced
this pull request
Jun 29, 2026
Contributor
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #891 +/- ##
===========================
===========================
☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
New plugin exposes a per-flow store of context entries keyed by symbol, with human-readable descriptions, registered transforms for derived values, and a subscribe API for external automation/devtools. Flow end freezes the active store into a snapshot pushed onto a history stack; transforms and subscribers persist across flow rotations. StateContextPlugin sits on top of ContextPlugin and publishes six standard keys (flow id/state, view id/view, data, status) plus an aggregated `player.state` roll-up backed by a transform that reads from each leaf key. Includes JVM (Kotlin) and iOS (Swift) wrappers that expose the name-based bridge API to non-TS consumers; Package.swift registers the new iOS target. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Plugin Extends the ContextPlugin family with callable actions, validation state, and typed cross-platform reads: - Actions as function-valued context entries (no separate action registry): StateContextPlugin publishes scoped, live-controller-bound actions — `player.state.flow.transition(value)` and `player.state.data.set(binding, value)` — read back as directly-callable functions on every platform. - Validation state: a `player.validation` entry (and aggregate field) mirroring the validation controller via a passive, side-effect-free read (canTransition + per-binding responses). Populated by the rendering layer's binding tracking. - Typed snapshot reads: FrozenContextSnapshot.get(key)/get(name) mirrors live context access; functions captured at flow end become throwing tombstones. - Native parity: JVM `get<T>(name)` and iOS `get(name:as:)` deserialize entries (incl. function members) into typed PlayerStateContext; typed list()/history() and StateContextPlugin wrappers on both platforms. Makes NodeSerializableFunction public with a lazy node/serializer so the JVM wrapper can cache its JS member-function delegates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Use shorthand optional binding (guard let x) across ContextPlugin and FrozenContextSnapshot. - Rename the single-char `s` decode binding to `string` (identifier_name). - Lift ContextValidation out of Validation to a top-level type (nesting). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
372ab6d to
d3b5cc8
Compare
Member
Author
|
/canary |
Contributor
Benchmark ResultsComparison against baseline from
|
| Benchmark | Current | Baseline | Change |
|---|---|---|---|
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.bar |
805.90K ops/s | 792.61K ops/s | +1.7% |
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets.1.name |
477.59K ops/s | 447.84K ops/s | +6.6% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets.01.name |
472.96K ops/s | 452.53K ops/s | +4.5% |
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets['01'].name |
451.55K ops/s | 461.73K ops/s | -2.2% |
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets[01].name |
491.00K ops/s | 464.71K ops/s | +5.7% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets[name = "frodo"].type |
275.17K ops/s | 214.51K ops/s | +28.3% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["name" = "sprinkles"].type |
231.53K ops/s | 189.71K ops/s | +22.0% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["isDog" = false].type |
284.11K ops/s | 271.18K ops/s | +4.8% |
core/player/src/binding/__tests__/parser.bench.ts > parser benchmarks > Resolving binding: foo.pets["isDog" = true].type |
306.23K ops/s | 228.34K ops/s | +34.1% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.bar |
583.00K ops/s | 484.95K ops/s | +20.2% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets.1.name |
331.72K ops/s | 259.12K ops/s | +28.0% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets.01.name |
378.01K ops/s | 279.20K ops/s | +35.4% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets['01'].name |
335.79K ops/s | 285.38K ops/s | +17.7% ✅ |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets[01].name |
374.50K ops/s | 363.01K ops/s | +3.2% |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets[name = "frodo"].type |
231.96K ops/s | 243.94K ops/s | -4.9% |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["name" = "sprinkles"].type |
189.96K ops/s | 200.91K ops/s | -5.5% |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["isDog" = false].type |
231.40K ops/s | 237.34K ops/s | -2.5% |
core/player/src/binding/__tests__/parser.bench.ts > binding creation benchmarks > Resolving binding: foo.pets["isDog" = true].type |
237.16K ops/s | 230.38K ops/s | +2.9% |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = 1 + 3 (sync) |
401.01K ops/s | 431.98K ops/s | -7.2% |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = 1 + 3 (async) |
329.13K ops/s | 256.47K ops/s | +28.3% ✅ |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: conditional(true, true, false) (sync) |
419.10K ops/s | 446.50K ops/s | -6.1% |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: conditional(true, true, false) (async) |
360.15K ops/s | 411.46K ops/s | -12.5% |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional({{bar}} > 0, true, false) (sync) |
206.01K ops/s | 187.69K ops/s | +9.8% ✅ |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional({{bar}} > 0, true, false) (async) |
194.84K ops/s | 176.75K ops/s | +10.2% ✅ |
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) |
166.31K ops/s | 153.94K ops/s | +8.0% ✅ |
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) |
152.98K ops/s | 146.44K ops/s | +4.5% |
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) |
274.35K ops/s | 274.66K ops/s | -0.1% |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = asyncTestFunction(1) (sync) |
311.15K ops/s | 304.58K ops/s | +2.2% |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = asyncTestFunction(1) (async) |
277.19K ops/s | 250.35K ops/s | +10.7% ✅ |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: asyncTestFunction(1) (sync) |
840.47K ops/s | 802.33K ops/s | +4.8% |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: asyncTestFunction(1) (async) |
668.53K ops/s | 571.23K ops/s | +17.0% ✅ |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(!{{bar}} == false, await(asyncTestFunction(1)), false) (sync) |
178.64K ops/s | 155.77K ops/s | +14.7% ✅ |
core/player/src/expressions/__tests__/performance.bench.ts > Expression Parsing/Execution Benchmark > Parsing: {{foo}} = conditional(!{{bar}} == false, await(asyncTestFunction(1)), false) (async) |
137.41K ops/s | 128.38K ops/s | +7.0% ✅ |
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > initial resolve |
603.83 ops/s | 611.80 ops/s | -1.3% |
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > Resolving from cache |
14.97K ops/s | 18.52K ops/s | -19.2% |
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > data changes |
2.45K ops/s | 2.99K ops/s | -18.0% |
core/player/src/view/resolver/__tests__/index.bench.ts > resolver benchmarks > data changes slow |
551.77 ops/s | 635.20 ops/s | -13.1% |
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 |
13.94K ops/s | 14.01K ops/s | -0.6% |
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 5 times |
13.86K ops/s | 12.49K ops/s | +10.9% ✅ |
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 10 times |
10.01K ops/s | 10.49K ops/s | -4.6% |
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 50 times |
3.29K ops/s | 3.25K ops/s | +1.2% |
plugins/async-node/core/src/__tests__/index.bench.ts > async node benchmarks > Resolve Async Node 100 times |
1.51K ops/s | 1.78K ops/s | -15.4% |
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 1 times |
7.56K ops/s | 5.91K ops/s | +27.9% ✅ |
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 5 times |
8.28K ops/s | 7.91K ops/s | +4.7% |
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 10 times |
7.13K ops/s | 6.92K ops/s | +3.0% |
plugins/async-node/core/src/__tests__/transform.bench.ts > async transform benchmarks > Resolve Async Node 50 times |
2.44K ops/s | 2.60K ops/s | -6.1% |
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 | -2.1% |
react/player
| Benchmark | Current | Baseline | Change |
|---|---|---|---|
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 1 ReactAssets |
577.88 ops/s | 532.19 ops/s | +8.6% ✅ |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 1 ReactAssets |
881.42 ops/s | 742.64 ops/s | +18.7% ✅ |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 5 ReactAssets |
604.03 ops/s | 529.03 ops/s | +14.2% ✅ |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 5 ReactAssets |
933.02 ops/s | 787.69 ops/s | +18.5% ✅ |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 10 ReactAssets |
565.54 ops/s | 530.01 ops/s | +6.7% ✅ |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 10 ReactAssets |
871.33 ops/s | 701.94 ops/s | +24.1% ✅ |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 50 ReactAssets |
489.96 ops/s | 426.81 ops/s | +14.8% ✅ |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 50 ReactAssets |
217.48 ops/s | 237.77 ops/s | -8.5% |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Render asset nested in 100 ReactAssets |
377.42 ops/s | 329.70 ops/s | +14.5% ✅ |
react/player/src/asset/__tests__/index.bench.tsx > ReactAsset benchmarks > Bubble errors nested in 100 ReactAssets |
120.86 ops/s | 78.58 ops/s | +53.8% ✅ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces the ContextPlugin family — a per-flow store that lets external consumers (automation, devtools, native hosts) read, derive, observe, and drive Player state without tapping every controller hook. Two commits: the base store, then read/write actions + validation + typed native reads.
ContextPlugin— named entries keyed by symbol; transforms for derived values; subscribe API; per-flow history snapshots (with rotation on flow end).StateContextPlugin— mirrors runtime state and publishes an aggregatedplayer.state(PlayerStateContext) with construct-scoped, callable actions (flow.transition,data.set) andvalidationstate.get<T>(name)/get(name:as:)) deserialize entries, including function members, into typed objects;list(),history(), and typedsnapshot.get(name).Notable behaviors
Cross-cutting change (please review)
jvm/core/.../NodeSerializableFunction.ktis madepublicwith a lazy node/serializer so the JVM wrapper can cache its JS member-function delegates. Backward-compatible (eager path preserved), but it's core bridge code.Testing
Core TS (vitest/eslint/typecheck), JVM (test + ktlint), and iOS tests all green.
🤖 Generated with Claude Code
📦 Published PR as canary version:
0.16.0--canary.891.38216Try this version out locally by upgrading relevant packages to 0.16.0--canary.891.38216