Skip to content

Sync inbox lifecycle state across devices - #94

Merged
badcuban merged 1 commit into
mainfrom
thread-lifecycle-sync
Jul 31, 2026
Merged

Sync inbox lifecycle state across devices#94
badcuban merged 1 commit into
mainfrom
thread-lifecycle-sync

Conversation

@badcuban

Copy link
Copy Markdown
Collaborator

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.setthread.done-override-set, projected to done_override / done_override_at (migration 042)
  • thread.seen.setthread.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, 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

  • Decider/projector/engine tests for the new commands (backwards seen accepted, missing thread rejected, updatedAt untouched), overlay merge + retirement tests, legacy-migration read tests
  • vp fmt, vp lint, vp run typecheck green
  • Full unit suites green (server 1676 tests, web 2000 tests) plus the full browser suite (353 tests)

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.
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
threadlines Skipped Skipped Jul 30, 2026 11:37pm

Request Review

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Jul 30, 2026
@badcuban
badcuban merged commit 04f4a91 into main Jul 31, 2026
15 checks passed
@badcuban
badcuban deleted the thread-lifecycle-sync branch July 31, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant