feat: optimistic UI updates with chain-truth reconciliation#15
Open
Sentinel-Bluebuilder wants to merge 2 commits into
Open
feat: optimistic UI updates with chain-truth reconciliation#15Sentinel-Bluebuilder wants to merge 2 commits into
Sentinel-Bluebuilder wants to merge 2 commits into
Conversation
After a mutating action (link/unlink node, add subscriber, register
provider) the operator had to wait out the read-cache TTL plus chain
indexing before the change appeared — "added nodes show after a big
delay", "subscriber count stale", etc.
Frontend: apply the change optimistically, then reconcile against chain
truth on a short delay and on a bounded retry schedule, so the UI is
instant but self-corrects if the chain disagrees.
- optimisticThenReconcile(url, { mutate, render, page }) — generic
optimistic-then-refetch wrapper
- reconcileProviderViews / reconcilePlanNodes / reconcileSubscriberViews
— view-specific reconcilers with backoff (attempts/baseMs)
- seedYourNodesWithAdded — show just-linked nodes immediately in Your Nodes
Backend: invalidate the operator-scoped read caches the moment a sub
lands so the reconcile refetch sees fresh data instead of a 30-120s
stale snapshot.
- invalidateSubscriberCaches(planId) drops planMembers/ownSub/planStats/
uniqueWallets for the operator
- called on subscribe, add-subscriber, and add-subscribers success
- getUniqueWallets wrapped in cached() (120s TTL) so its key exists to
be invalidated and the up-to-10k subscription query stops re-firing
on every plan detail view
This was referenced Jun 21, 2026
The plan-card pill (totalNodes) snapped back to the PRE-link count after adding or removing nodes: the chain indexes a link/unlink 1-6s after the broadcast, so any /api/my-plans refetch (or background SWR revalidate) in that window returned the stale count and clobbered the optimistic bump. Add a per-plan sticky override (S.pendingNodeCount): - setPendingNodeCount(planId, value, dir) records the optimistic value. - applyPendingNodeCounts(plans) re-paints that value IN PLACE on every fetched plans array until the chain count reconciles (>= for adds, <= for removes), with a hard 30s safety timeout so a stuck override can never pin a wrong number forever. Returns whether anything is still pending so refetchMyPlans keeps polling. Wire it through: - refetchMyPlans now waits before the first fetch when an override is pending (give the indexer a head start), applies overrides each attempt, and keeps polling until reconciled (attempts 4 -> 8). - _ensurePlansLoaded applies overrides on both the SWR onRefresh path and the initial fetch so a background revalidate can't snap the card back. - linkBatch / unlinkBatch register an 'up' / 'down' override alongside the existing optimistic bump. Complements the existing reconcilePlanNodes() node-list reconciliation; this fixes the card-pill count specifically.
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.
What
Makes mutating actions feel instant by updating the UI optimistically, then
reconciling against chain truth on a short delay + bounded retry — and
invalidating the operator's read caches server-side so the reconcile refetch
sees fresh data.
Why
After link/unlink, add-subscriber, or provider-register, operators had to wait
out the read-cache TTL (30–120s) plus chain indexing before the change
showed. Symptoms: "added nodes show after a big delay", stale subscriber counts,
provider registration not appearing immediately.
Frontend (
public/index.html)optimisticThenReconcile(url, { mutate, render, page, delayMs })— genericapply-now-then-refetch wrapper.
reconcileProviderViews,reconcilePlanNodes(planId, predicate, {attempts, baseMs}),reconcileSubscriberViews— view-specific reconcilers with backoff thatre-poll until chain truth matches the optimistic state (or attempts run out).
seedYourNodesWithAdded(addrs)— surfaces just-linked nodes in Your Nodesimmediately.
Backend (
server.js)invalidateSubscriberCaches(planId)— drops the operator-scopedplanMembers/ownSub/planStats/uniqueWalletscaches, keyed bygetAddr()to match how they were written.paths (alongside the existing
invalidatePlanSubs).getUniqueWalletswrapped incached()(120s TTL, plan-keyed) so theup-to-10k subscription query stops re-firing on every plan-detail view — and
so the
uniqueWallets:<id>key actually exists for the invalidation above todrop.
Notes
cache wrapper); no existing route behavior changes on a cache miss.
🤖 Generated with Claude Code