Skip to content

Add ContextPlugin: shared Player state for external consumers#891

Draft
sugarmanz wants to merge 3 commits into
mainfrom
context-plugin
Draft

Add ContextPlugin: shared Player state for external consumers#891
sugarmanz wants to merge 3 commits into
mainfrom
context-plugin

Conversation

@sugarmanz

@sugarmanz sugarmanz commented Jun 29, 2026

Copy link
Copy Markdown
Member

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 aggregated player.state (PlayerStateContext) with construct-scoped, callable actions (flow.transition, data.set) and validation state.
  • Native parity (JVM + iOS) — typed reads (get<T>(name) / get(name:as:)) deserialize entries, including function members, into typed objects; list(), history(), and typed snapshot.get(name).

Notable behaviors

  • Actions are function-valued entries, bound to live controllers — no separate action registry. Read back as directly-callable functions across the bridge.
  • Validation is mirrored via a passive, side-effect-free read (never triggers validation). Populated only for bindings the rendering layer tracks.
  • Flow end freezes the store into a history snapshot then rotates; captured functions become throwing tombstones. Post-terminating-transition live reads are racy by design — read the snapshot for end-of-flow state.

Cross-cutting change (please review)

jvm/core/.../NodeSerializableFunction.kt is made public with 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.38216

Try this version out locally by upgrading relevant packages to 0.16.0--canary.891.38216

@sugarmanz sugarmanz added the minor Increment the minor version when merged label Jun 29, 2026
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 115.23kB (2.05%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
plugins/context/core 115.23kB 115.23kB (100%) ⬆️

@sugarmanz

Copy link
Copy Markdown
Member Author

/canary

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

Copy link
Copy Markdown
Member Author

/canary

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

Copy link
Copy Markdown
Contributor

Build Preview

Your PR was deployed by CircleCI #38216 on Mon, 29 Jun 2026 15:00:02 GMT with this version:

0.16.0--canary.891.38216

📖 Docs (View site)

@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (55defef) to head (372ab6d).
⚠️ Report is 104 commits behind head on main.

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

☔ 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.

sugarmanz and others added 3 commits June 29, 2026 08:13
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>
@sugarmanz

Copy link
Copy Markdown
Member Author

/canary

@intuit-svc

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 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% ✅

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.

2 participants