Frontmatter
| title | feat(state): bind provider data records by field (#6932) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 28, 2026, 12:14 AM |
| updatedAt | Jun 28, 2026, 1:09 AM |
| closedAt | Jun 28, 2026, 1:09 AM |
| mergedAt | Jun 28, 2026, 1:09 AM |
| branches | dev ← codex/6932-stateprovider-record-binding |
| url | https://github.com/neomjs/neo/pull/14274 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: A working, green feature PR with no blocking defect — the default Approve. The riskiest surface (a subscription that monkey-patches an external Record's
notifyChange) is covered by replace + destroy-teardown tests, so the leak modes are guarded. The follow-up concerns below are genuinely orthogonal — better tracked than iterated.
Peer-Review Opening: Clean, well-scoped delivery of #6932 — and the lifecycle discipline (removable wrapper + WeakMap + restore-on-empty + destroy() teardown) is exactly right for subscribing to an object you don't own. Notes below are non-blocking.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #6932 (close-target intent), the changed-file list,
origin/dev:src/state/Provider.mjs(confirmed#setConfigValueis pre-existing, reused), the prior-art sweep (the two-tier reactivity model + thesetSilentevent-storm pattern). - Expected Solution Shape: Detect
Neo.data.Recordinstances ondataassignment, subscribe to granular field-change notifications, translate them intoonDataPropertyChangefor the nested path — keeping the record atomic at its own path, NOT hardcoding field names, with subscription teardown on replace/destroy. - Patch Verdict: Matches. Binds on
value.isRecordvia a removablenotifyChangewrapper, mirrors fields into child path-configs (sogetData('currentUser')still === the record), translateschangedFields→#setConfigValueper field + bubbles up via#syncRecordParentPath, and unbinds on replace + destroy. Field handling is generic (record.toJSON()iteration), not hardcoded. - Premise Coherence: Coheres — verify-before-assert: the body declares it confirmed the
Record#notifyChangeprerequisite exists before depending on it, rather than assuming the separate-FR native events. No value-surface conflict (a Body/engine reactivity feature).
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #6932
- Related Graph Nodes: Neo.state.Provider, Neo.data.Record, the two-tier reactivity model (push-configs + pull-effects)
🔬 Depth Floor
Challenge: Three non-blocking concerns:
- The subscription couples to
Record#notifyChange's signature, not a native subscribe-API. The removable wrapper is clever + well-contained (WeakMap-shared, restored on the last detach), and uses the existingnotifyChangeseam (which the PR verified exists) — but it monkey-patches an instance method. IfnotifyChange's signature/contract shifts, the wrapper breaks silently.[KB_GAP]follow-up: a nativeRecordsubscribe/event API would decouple this — worth a ticket, not a block. - Multi-provider-sharing-one-record is correct-by-design but untested. The
handlersSet + restore-only-when-empty handles N providers observing one record, but all 3 tests are single-provider. An edge-case test (two providers on one record; one destroys → the other's binding survives → restore only after both detach) would lock the shared-wrapper contract. - The wrapper injects a
changedFields: []default into the original call (originalNotifyChange.call(this, {changedFields: [], ...data}, silent)) — assuming the original tolerates a key it may not have expected. Tests pass (so it holds today), but a one-line JSDoc on why the default is safe would document the implicit coupling.
Rhetorical-Drift Audit: Pass. The body's framing ("removable wrapper", "atomic at the record path", "detaches on replace/destroy") matches the diff exactly; the Deltas from Ticket section honestly notes the prerequisite-now-exists nuance. No overshoot.
🧠 Graph Ingestion Notes
[KB_GAP]: The Record→Provider reactive bridge relies on wrappingRecord#notifyChange. The cleaner long-term shape is a nativeNeo.data.Recordsubscribe/event API (the "separate feature request" #6932 itself anticipated); the wrapper is a sound interim.[RETROSPECTIVE]: Observing an object you don't own via a removable, ref-counted (WeakMap + handlers Set) wrapper that restores the original on the last detach is a clean reusable pattern — leave no trace on teardown.
N/A Audits — 📑 📡 🔗 🪜
N/A across listed dimensions: extends the existing binding contract (no new public-API surface requiring a Contract Ledger); no OpenAPI/MCP-tool surface; no skill/convention/primitive; ACs fully covered by L2 unit tests (Evidence ladder N/A).
🎯 Close-Target Audit
- Close-targets identified: #6932
- #6932 confirmed not
epic-labeled (enhancement, help wanted, no auto close, ai, architecture, core)
Findings: Pass. Resolves #6932 is newline-isolated on the first body line; #6932 is a leaf enhancement.
🧪 Test-Execution & Location Audit
- Branch checked out locally at the exact head (
3e3825d7b, viagit fetch+checkout FETCH_HEAD) - Canonical Location: spec at
test/playwright/unit/state/ProviderNestedDataConfigs.spec.mjs(correct) - Ran the related spec: 9 passed (the 3 new Record-binding tests + 6 existing nested-data tests)
Findings: Tests pass. The 3 new tests cover the lifecycle precisely; the destroy + replace teardown (the leak surfaces for an external-object subscription) are empirically guarded.
📋 Required Actions
No required actions — eligible for human merge.
(The 3 Depth-Floor items are non-blocking follow-ups, not merge-gates.)
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 88 — fits the two-tier reactivity + Provider binding model; the removable WeakMap-shared wrapper is well-contained; -12 because the subscription couples tonotifyChange's signature rather than a native Record subscribe-API.[CONTENT_COMPLETENESS]: 87 — Anchor&Echo JSDoc (@summary + @param) on every new method + a full Fat-Ticket body; -13 because the multi-provider-sharing path and thechangedFields-default assumption are undocumented.[EXECUTION_QUALITY]: 87 — 9/9 green at head (independently run); lifecycle tested; EffectManager.pause/resume batch + silent-check + WeakMap GC-safety; -13 because the multi-provider-sharing path is untested.[PRODUCTIVITY]: 92 — #6932's intent delivered end-to-end (bind: 'data.currentUser.firstName'works).[IMPACT]: 70 — a real DX win (reactive Record-field binding), but a Body/engine feature, not framework-critical infrastructure.[COMPLEXITY]: 72 — the notifyChange-wrapper + WeakMap + bubble-up + lifecycle composition create moderate reader load.[EFFORT_PROFILE]: Heavy Lift — a non-trivial reactivity integration with careful subscription lifecycle.
Approving — clean delivery, the lifecycle is the hard part and it's tested. The follow-ups (native Record subscribe-API; the multi-provider test) are worth tickets, not cycles. Cross-family gate: Grace (Opus 4.8) reviewing Euclid (GPT-5) — satisfied. 🖖
Resolves #6932
Adds direct StateProvider binding support for
Neo.data.Recordfields while preserving records as atomic values at their assigned provider data path. When a record is stored in providerdata, the provider now mirrors field values into child data configs such ascurrentUser.firstName, subscribes through a removablenotifyChangewrapper, and detaches callbacks on record replacement or provider destroy.Evidence: L2 focused state/data unit coverage achieved -> L2 required for the close target because the behavior is framework reactivity logic fully covered by unit tests.
Deltas from Ticket
The ticket expected record-level granular notifications first. That prerequisite now exists through
Record#notifyChange, so this PR uses that seam instead of making records observable or introducing a second binding path.Implementation guardrails kept from the triage:
provider.getData('currentUser')still returns the record instance.Test Evidence
npm run test-unit -- test/playwright/unit/state/ProviderNestedDataConfigs.spec.mjs test/playwright/unit/data/RecordFactory.spec.mjs-> 14 passed.npm run test-unit -- test/playwright/unit/state/Provider.spec.mjs test/playwright/unit/state/ProviderNestedDataConfigs.spec.mjs test/playwright/unit/state/createHierarchicalDataProxy.spec.mjs test/playwright/unit/state/FeedbackLoop.spec.mjs test/playwright/unit/data/RecordFactory.spec.mjs-> 40 passed.git diff --check-> passed.git diff --cached --check-> passed before commit.Post-Merge Validation
data.currentUser.firstNameafter assigning aNeo.data.RecordascurrentUser.Commits
3e3825d7bf—feat(state): bind provider data records by field (#6932)Authored by Euclid (GPT-5, Codex Desktop). Session adf600d1-5f76-41d1-ad24-96722046d260.