Sync inbox lifecycle state across devices - #94
Merged
Conversation
The sidebar's Active/Wrapped split lived in each browser's localStorage, so a phone opened through the relay disagreed with the desktop about which threads were filed away and which completions were unread. Both pieces now live on the thread, on the server: - thread.done-override.set -> thread.done-override-set, projected to done_override / done_override_at - thread.seen.set -> thread.seen-set, projected to last_seen_at Neither event touches the thread's updatedAt. The inbox weighs these stamps against real work activity, and auto-wrap idles off the same value, so filing or reading a thread must not read as activity. The decider stores the client's stamp verbatim for the same reason, and seen is last-write-wins because mark-unread deliberately walks the stamp backwards. The web store keeps both only as in-memory optimistic overlays: a write shows instantly, then retires once the server value moves off the baseline it was made against, and a rejected dispatch drops it. On first connect each environment hands its pre-sync localStorage state to the server once, filling only gaps, then clears those keys. Also names the generated WS RPC client type rather than inferring it: the inferred form inlines every command payload and two more tipped it past the type serialization limit.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
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.
Problem
The sidebar's Active/Wrapped split and unread state lived in each browser's localStorage, so a phone opened through the relay disagreed with the desktop: threads marked done on the desktop still showed active on the phone, reopened threads re-filed themselves, and unread pills never agreed.
Change
Both pieces of state move onto the thread, server-side, through the same command → event → projection path pinning and archiving already use:
thread.done-override.set→thread.done-override-set, projected todone_override/done_override_at(migration 042)thread.seen.set→thread.seen-set, projected tolast_seen_atNeither event touches the thread's
updatedAt: the inbox weighs these stamps against real work activity and auto-wrap idles off the same value, so filing or reading a thread must not read as activity. The decider stores the client's stamp verbatim, and seen is last-write-wins because mark-unread deliberately walks the stamp backwards.On the web, the zustand store keeps both only as in-memory optimistic overlays: a write shows instantly, retires once the server value moves off the baseline it was written against, and rolls back if the dispatch is rejected. On first snapshot each environment migrates its pre-sync localStorage state to the server once, filling only gaps the server has no newer answer for.
Side effect worth knowing: unread state now survives a reload (the visited map was in-memory only before, so reloading marked everything read).
Also names the generated WS RPC client type explicitly instead of inferring it — the inferred form inlines every command payload and two more union members tipped it past TS's type-serialization limit.
Testing
updatedAtuntouched), overlay merge + retirement tests, legacy-migration read testsvp fmt,vp lint,vp run typecheckgreen