watch: the rail loads from memory, then checks in the background for tokens bought since - #825
Merged
Merged
Conversation
…tokens bought since
Nate, on the /markets rail: "since the watchlist was loaded first time, I have
since bought more tokens… first load from memory, but background check for more
tokens owned".
Memory: every holdings read that answers is remembered per wallet in this
browser (lib/watchlists writeHeldSnapshot, one write site in lib/held-read), so
a cold load paints each row's position with the lists instead of popping it in a
read later. Memory paints; only a live read arms — peekHeld, lib/use-held and
every Sell chip stay live-only, and so does the card door.
Background check: the rail's minute poll now runs the same reconcile as the
visit, gated by heldReconcileReason — a first read for the wallet, a symbol the
last reconcile never saw, or a landed purchase asking. A read with nothing new
reprices and writes nothing. The autofill used to run once per mount, so a token
bought after the page loaded waited for the next navigation.
Found on the way, and present on main: a page runs useWatchlists() twice (the
rail and the Morning tape), they share the module-level reconcile claim, and
whichever won the race kept the filled list to itself — the rail said "Nothing
watched yet." for the whole visit while the tape beside it read "2 SYMBOLS" off
the same fill. The tape now takes { holdings: false } (one holdings reader per
page) and a fill is announced to the other instance, scoped to whose lists it
is and never back to the announcer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Nate, with a screenshot of the /markets rail: "since the watchlist was loaded first time, I have since bought more tokens, can we load in the background to see if the user has more tokens they purchase, first load from memory, but background check for more tokens owned".
Both halves, plus a bug the first drive turned up: the rail never showed its own autofill.
Memory first
Every holdings read that answers is remembered in this browser (
lib/watchlistswriteHeldSnapshot, keypantessa.wallet.held.v1, per wallet, newest 4 wallets, dropped after a day). On the next load the rail paints each row's position from that memory while its own read is in flight, instead of popping the column in seconds later.Memory paints; only a live read arms. Every Sell chip goes through
lib/sell-gate→lib/use-held→peekHeld, and none of them read the snapshot — nothing is ever offered on a memory of a token that was since sold. The card door (walletEmpty) waits for the live read too.The background check
The rail already re-read holdings once a minute on a visible tab, but only to reprice. The autofill ran once per mount, so a token bought after the page loaded waited for the next navigation.
Now the poll runs the same reconcile as the visit, gated by one rule (
heldReconcileReason):recheckWallet)A removed row still stays off: the ledger is checked before anything is added, on both paths.
FOUND on main: the rail didn't show what the autofill added
Driving the first cut, the guest rail said "Nothing watched yet." for the whole visit while
localStorageheld["ETH","AAPL"]and the Morning tape beside it read "2 SYMBOLS" off the same fill. Reproduced onorigin/main(control build, same drive) — it is not new here.Cause: two
useWatchlists()instances mount on /markets (the rail and the tape). They share the module-level reconcile claim, so whichever won the race did the fill and kept the new list in its own React state; the other one showed the list as it was at load.Fixed two ways:
useWatchlists({ holdings: false }): it keeps the lists without reading the wallet or reconciling a second time.Gates
tsc --noEmitclean,npm run buildcleantest:api— 3123 passed / 2 failed — the standing known-red (router select) and main's stalearc (live)WETH pin (Arc's WETH/USDC has liquidity now; A quote is not a price unless the pool can absorb it #820's territory, not this branch's). 8 new checks, and two re-pinned on purpose: the poll pin said the visible-tab clock "never reconciles", and the card door's forced re-read was pinned to a deleted timestamp.No DDL, no env, no new dependency. The API route is unchanged.
Calls for you
fresh=1, so a tab-flipper can't amplify reads onto six chains. Worst case is ~45s staler than a forced read.Files
lib/watchlists.ts(the memory +heldReconcileReason, both pure) ·lib/held-read.ts(one write site) ·components/markets/watchlist/useWatchlists.ts(one shared reconcile, the poll calls it, the fill announcement,holdings: false) ·components/markets/ai/MorningTape.tsx(one line) ·scripts/test-api.ts.🤖 Generated with Claude Code