Add a live data layer: merge changed rows by id instead of reloading lists - #18
Merged
Merged
Conversation
…lists 0.2.0-next.19 tried to fix rows popping out and back in by withholding the doorbell reload while a client's own writes were in flight. next.20 reverted it and named the real problem: any design that replaces the whole list from a server snapshot has a window in which that snapshot is stale, so the fix is to stop replacing the list. This does that. LiveEntityStore (live.ts) is the sync state machine, and useEntity (bool-sdk/react) is its React surface. Four defects in the hand-wired load() + subscribe(() => load()) pattern, which the system prompt taught every app to rebuild: - Replacing the list let a stale reload overwrite rows already on screen. Now changes apply as deltas merged by id, so there is no snapshot to be stale. - No coalescing: the platform trigger fires per row, so a 50-row write rang the doorbell 50 times and cost 50 full-list reloads. Pings now batch into one keyed fetch per 50ms window. - No response ordering: two reloads could land out of order and rewind the view. Full loads now carry a monotonic sequence and stale responses are dropped. - A full reload wiped optimistic rows whose writes hadn't committed. Pending writes are now an overlay on top of committed state; rollback drops the overlay. Reconciling costs as little as the ding allows: DELETE applies with no fetch, a ding carrying the row applies with no fetch, and anything else fetches only the changed rows by id — through the gateway, so auth and telemetry still apply. A ding with no id (older platform trigger) degrades to one coalesced full reload. Filtered views self-maintain via matchesFilter, a client-side evaluation of the same DSL entities.ts translates to PostgREST, including SQL's three-valued null semantics so a delta-applied row matches what the next full load would return. Co-Authored-By: Claude Opus 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.
Does what
0.2.0-next.20concluded was the real fix for rows popping out and back in:next.19shrank the window (reload-hold) and was reverted. This stops replacing the list.What's new
LiveEntityStore(src/live.ts) is the sync state machine;useEntity(bool-sdk/react) is its React surface:One call replaces the
useEffect+useState+load()+ subscription pattern the system prompt taught every app to hand-wire.The four defects it fixes
FOR EACH ROW→ 50 rows = 50 full reloadsReconcile cost
DELETE→ applied locally, zero fetchesrow(future private-channel doorbell) → zero fetchesid(older platform trigger) → one coalesced full reloadFiltered views self-maintain:
matchesFilterevaluates the same DSLentities.tstranslates to PostgREST, including SQL three-valued null semantics, so a delta-applied row matches what the next full load would return.Testing
bun test— 134 pass, 0 fail.bun run typecheckclean. 24 new tests insrc/live.test.tscover coalescing, delete-without-fetch, row-on-ding, RLS-hidden rows leaving the view, filter transitions, stale-response dropping (via a gated deferred), optimistic survival across a concurrent refetch, rollback on every mutation, and self-echo dedupe.Paired with
Platform PR restoring
idto the doorbell payload and adding the capability-gated prompt variant.🤖 Generated with Claude Code