fix(bindx): materialize an embedded has-many before reading or mutating it - #76
Merged
Merged
Conversation
…ng it
A has-many loaded as embedded data on its parent has no state in the store
until something materializes it. `items` and `getById` do that; isDirty and
every mutator did not. So a consumer that never iterated the list read
isDirty === false and had its disconnect/connect/add/remove land on a state
nobody reads — silently, with no error.
FormHasManyRelationScope is the case in this repo: it renders
`dirty={relation.isDirty}` without ever touching `items`, so its test
"tracks dirty state from has-many relation" has been failing on main. That
failure was invisible to CI, because the `test` script covers only
tests/unit, tests/react and tests/cases — not packages/*/tests.
Every entry point that depends on the state existing now goes through the
same guard, which is idempotent. The regression test lives under tests/unit
so CI actually runs it; two of its four cases fail without this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiniFfaE4gb5EuQpQ3Ncee
matej21
force-pushed
the
fix/hasmany-embedded-materialization
branch
from
August 20, 2026 09:27
427127f to
18d10e4
Compare
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.
Fixes the one test that has been failing on
main.The bug
A has-many loaded as embedded data on its parent — what a query with a nested selection produces — has no state in the store until something materializes it.
HasManyListHandle.itemsandgetByIdcallmaterializeEmbeddedItems();isDirtyand every mutator did not.So a consumer that never iterated the list:
isDirty === falseno matter what, anddisconnect/connect/add/remove/move/resetland on a state nobody reads.Silently. No error, no warning.
The irony is that the method's own doc comment already anticipated exactly this class of mistake:
getByIdwas added to the guard; the mutators andisDirtywere not.The victim in this repo
FormHasManyRelationScoperendersdirty={relation.isDirty}and never touchesitems, sopackages/bindx-form/tests/formRelations.test.tsx > tracks dirty state from has-many relationhas been failing onmain. I bisected it back to3c2fd0d— it predates the recent store work and is not fallout from #49/#50/#56.CI never reported it, because the
testscript covers onlytests/unit,tests/react,tests/casesandtests/*.test.*— notpackages/*/tests,tests/bindx-client,tests/repeaterortests/shared. Worth closing that gap separately.The fix
Every entry point that depends on the state existing now goes through the same idempotent guard, and the doc comment says so, so a new entry point has a rule to follow rather than a precedent to guess at.
Tests
tests/unit/handles/hasManyEmbeddedMaterialization.test.ts— deliberately undertests/unitso CI actually runs it. Four cases; two fail without the change (verified by reverting the source file):disconnectmarks the relation dirty without reading items first — fails withoutitemsread — fails withoutconnectmarks the relation dirty without reading items first — passes either way, kept as coverageitemsfirst still works — the idempotency controlpackages/bindx-form/tests/formRelations.test.tsxgoes 2 pass / 1 fail → 3 pass.Verification
Branch gated in isolation with its own
bun install: typecheck clean,bun run test1531 pass / 0 fail,bun test packages/170 pass / 0 fail (was 169 pass / 1 fail).Not verified
Only the embedded-list shape is exercised. A has-many loaded by its own separate query already has store state, so it was never affected; that path is covered by the existing suites rather than by anything new here. Whether the same gap exists on the has-one side is not addressed by this PR.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GiniFfaE4gb5EuQpQ3Ncee