Skip to content

feat(server): sync thread read status across clients - #5652

Open
t3dotgg wants to merge 1 commit into
mainfrom
t3code/server-side-thread-status
Open

feat(server): sync thread read status across clients#5652
t3dotgg wants to merge 1 commit into
mainfrom
t3code/server-side-thread-status

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 7, 2026

Copy link
Copy Markdown
Member

Thread read and unread state lived in web local storage, so browsers and devices could disagree about which completed threads had been seen.

This moves lastViewedAt into the event-sourced server projection and adds server-owned mark-viewed and mark-unread commands. Web and mobile now update the shared state, older servers are capability-gated, and view updates do not touch updatedAt or reorder threads. Existing threads remain read by default after migration.

Verified with focused decider, projection, persistence, command, reducer, sidebar, and UI-state tests (177 passing), plus scoped typechecks for contracts, client-runtime, server, web, and mobile.

Built with GPT-5.6-sol via the Codex harness in T3 Code.


Note

Medium Risk
Touches orchestration commands, projection, and migration backfill; upgrade backfill can briefly change unread semantics for existing threads until users view them again.

Overview
Thread read/unread state moves from per-browser local storage to the server, so web and mobile share the same lastViewedAt and sidebar badges stay consistent across devices.

The server gains threadViewStatus capability, thread.mark-viewed / thread.mark-unread commands, thread.view-status-updated events, and last_viewed_at on projection_threads (migration 041 backfills existing rows at upgrade time). The decider enforces optimistic concurrency on mark-viewed and never moves view timestamps backward; view updates do not bump updatedAt.

Clients call threadEnvironment.markViewed / markUnread when the capability is present (mobile on screen focus, web on open thread / wake ack / settle-archive paths). uiStateStore drops threadLastVisitedAtById; unread, “Woke”, and mark-unread menus use thread.lastViewedAt and hasUnseenWake. Client-runtime wires markViewed with queued supersededViewedAt for serialized views.

Reviewed by Cursor Bugbot for commit 19b837e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sync thread read/unread status across clients via server-owned view status

  • Adds thread.mark-viewed and thread.mark-unread commands to the orchestration layer, with a thread.view-status-updated event and lastViewedAt field persisted on threads in projection_threads.
  • Removes client-local read tracking (markThreadVisited, threadLastVisitedAtById) from uiStateStore; unread/woke indicators now derive from server-provided lastViewedAt.
  • Exposes a threadViewStatus capability flag from the server; all mark-viewed/mark-unread UI actions (sidebar context menus, wake acknowledgement, archive/settle) are gated on this capability and fall back gracefully when unsupported.
  • Adds migration 041_ProjectionThreadsLastViewedAt to add last_viewed_at to projection_threads and backfill existing rows with the current time.
  • Risk: Existing persisted threadLastVisitedAtById data in localStorage is silently dropped on next load; read state is reset to server state for all users after upgrade.

Macroscope summarized 19b837e.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 62199432-964b-4132-ba27-efffcc0117d6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 7, 2026
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
Comment thread apps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
Comment thread apps/server/src/orchestration/decider.ts
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 11.3 KiB 11.3 KiB +1 B (+0.0%) 15.1 KiB
Codex Thread snapshot wire 5.5 KiB 5.5 KiB +16 B (+0.3%) 7.3 KiB
Codex Live turn WebSocket wire 5.9 KiB 5.9 KiB −15 B (−0.2%) 7.8 KiB
Codex Live turn WebSocket decoded 49.7 KiB 49.7 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 16 16 0 (0.0%) 21
Claude Total thread wire 11.3 KiB 11.3 KiB +31 B (+0.3%) 15.1 KiB
Claude Thread snapshot wire 5.5 KiB 5.5 KiB +19 B (+0.3%) 7.3 KiB
Claude Live turn WebSocket wire 5.8 KiB 5.8 KiB +12 B (+0.2%) 7.8 KiB
Claude Live turn WebSocket decoded 50.6 KiB 50.6 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 16 16 0 (0.0%) 21

Baseline: db1507e · PR result: 19b837e · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.7 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. This PR introduces a new feature for server-synchronized thread read/unread status, including new commands, events, database migrations, and schema changes. The scope involves significant new runtime behavior. An unresolved Medium severity finding identifies a potential issue where clients can submit far-future timestamps to mark all future completions as read.

You can customize Macroscope's approvability policy. Learn more.

@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1e Compare August 8, 2026 08:01
Comment thread apps/web/src/hooks/useThreadActions.ts
Comment thread apps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1 Compare August 8, 2026 08:07
Comment thread apps/server/src/orchestration/decider.ts
@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededda Compare August 8, 2026 08:10
Comment thread apps/web/src/components/threadActionMenu.logic.ts Outdated
Comment thread apps/web/src/components/ChatView.tsx
@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466d Compare August 8, 2026 08:25
Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259 Compare August 8, 2026 08:35
Comment thread apps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0 Compare August 8, 2026 08:39
Comment thread apps/web/src/hooks/useThreadActions.ts
Comment thread packages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94 Compare August 8, 2026 08:49
@t3dotgg

t3dotgg commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

Terrible when compared to #5652

@t3dotgg

t3dotgg commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6f Compare August 8, 2026 10:47
threadId: ThreadId,
});

const ThreadMarkViewedCommand = Schema.Struct({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/orchestration.ts:674

ThreadMarkViewedCommand.viewedAt is documented as a server-owned boundary copied from thread completion/wake state, but the schema accepts any client-supplied IsoDateTime. Because the decider persists this value directly (only comparing it against the previous one), a client can submit a far-future timestamp and make all subsequent completions and wakes appear already read. The server should derive or validate this boundary against actual thread state rather than trusting the client value.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/contracts/src/orchestration.ts around line 674:

`ThreadMarkViewedCommand.viewedAt` is documented as a server-owned boundary copied from thread completion/wake state, but the schema accepts any client-supplied `IsoDateTime`. Because the decider persists this value directly (only comparing it against the previous one), a client can submit a far-future timestamp and make all subsequent completions and wakes appear already read. The server should derive or validate this boundary against actual thread state rather than trusting the client value.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0125c6f. Configure here.

Comment thread packages/client-runtime/src/state/threadCommands.ts
@saphid saphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotgg force-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837e Compare August 13, 2026 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). 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