Repository navigation
[HSF-1642] fix: resolve hook methods against the live subscription - #55
Merged
Merged
Conversation
useCollection/useDocument copied update/add/remove/set/delete/load/sync off the subscription at render time and re-copied them only when the selected slice changed. When a hook adopted a sibling's committed entry in acquire() (e.g. a loading-status hook mounted before a writer), or the entry's subscription was rebuilt after refCount hit 0, a value-equal slice kept the methods bound to the abandoned subscription. It has no snapshot, so writes were dropped (silently in production). Each method is now a wrapper built once per `shared` facade that calls getHandle() when invoked, so it reaches the current subscription and keeps its identity across rerenders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bump @hvakr/firestate from 0.2.1 to 0.2.2 to release the hook method binding fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 bug behind HSF-1642 (worked around in the app by flowcircuits/hvakr#8248). When a loading-status hook mounted before a writer on the same resource, the writer's
update/add/remove/set/load/syncstayed bound to an abandoned provisional subscription. That subscription has no snapshot, so writes were dropped, with no signal at all in production. Create Zone and Create System on a new project did nothing because of this. Hook order no longer matters.Summary
Each hook builds a provisional subscription during render. On commit, every hook after the first adopts the first one's entry (
acquire()setsent = committed). The returned handle copied its methods at render time and copied them again only when the selected slice changed, so a value-equal slice kept the stale methods. Examples: an empty collection's{}, a missing doc'sundefined, a writer-only selector.ent = committed): the wrappers go through the facade, so they reach the adopted entry.ent.sub = buildSub(ent)after refCount hits 0): a method held from before keeps its identity and calls the rebuilt subscription. I chose this because identity is tied to the facade (shared), which survives the rebuild. Changing identity would only help callers that list the method as a dependency. A method already captured in a closure would still hit the stopped subscription.getHandle(), which still neuters writers andsync.loadpasses through to the current subscription.sharedchanges (definition, path, or query). Before this fix it also changed silently mid-life, when the first snapshot rebound methods from the provisional to the adopted subscription.No options or flags. Contract text is updated in
AGENTS.md,docs/architecture.md, andREADME.md.Evidence
New block in
src/react/hooks.test.ts(onlyonSnapshotis mocked; queries and refs are real):masterupdate()undefinedadd()undefinedremove()(writer-only slice)set()undefinedupdate()(writer-only slice){ name: 'a' }load()reaches live sub, writers blockedupdateundefinedupdateState≠updateStateThe replacement case uses a re-keyed remount, not StrictMode, because
react-test-renderer18.3.1 never double-runs effects. The new hooks render while the entry is still live. The old hooks then release it (refCount 0), and the new ones re-acquire it, which takes the samebuildSubpath that StrictMode and offscreen take.Release
package.jsonis bumped to 0.2.2 in its ownchore(release)commit, like #53. The repo has no CHANGELOG file; release notes are generated from PR titles when the GitHub release is published. Suggested note: Fix hook methods (update/add/remove/set/load/…) staying bound to an abandoned subscription, so writes were dropped depending on hook mount order (HSF-1642).Follow-ups (out of scope)
warnNoSnapshot, and only outside production, so the caller can't tell the write was dropped. Proposal: report it through the store'sonErrorand/or return a result from the writers. That is an API change, so it is not built here.@hvakr/firestatein the app. The hook-order workaround from flowcircuits/hvakr#8248 can then be reverted. The app is not changed in this PR.refis still read when the handle is built. After adoption it can be the provisional subscription's ref object: same path andrefEqual, but a different identity. This doesn't affect correctness.Merge Danger
Door: two-way
Revert the commit to roll back. No data or storage changes.
Blast Radius: hooks
Every
useDocumentanduseCollectionhandle, including the generatedcreateFirestatehooks, now returns wrapper functions instead of the subscription's own functions. Calls and return values are unchanged. Code that compared a handle method by identity againstsubscription.getHandle().updatewould now see a different function; nothing in the repo does this. Disabled handles (enabled: false) also return per-hook no-op wrappers instead of the shared module-level no-ops. They are still stable across renders.🤖 Generated with Claude Code
Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.