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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

coderabbitaiBot 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-actionsgithub-actionsBot 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 threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/server/src/orchestration/decider.ts
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actionsBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB+1 B (+0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+16 B (+0.3%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−15 B (−0.2%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.3 KiB11.3 KiB+31 B (+0.3%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB+19 B (+0.3%)7.3 KiB
ClaudeLive turn WebSocket wire5.8 KiB5.8 KiB+12 B (+0.2%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (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

macroscopeappBot 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
t3dotggforce-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1eCompareAugust 8, 2026 08:01
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadapps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1CompareAugust 8, 2026 08:07
Comment threadapps/server/src/orchestration/decider.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededdaCompareAugust 8, 2026 08:10
Comment threadapps/web/src/components/threadActionMenu.logic.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466dCompareAugust 8, 2026 08:25
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259CompareAugust 8, 2026 08:35
Comment threadapps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0CompareAugust 8, 2026 08:39
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadpackages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94CompareAugust 8, 2026 08:49
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Terrible when compared to #5652

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6fCompareAugust 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.

🟡 Mediumsrc/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.

@cursorcursorBot 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 threadpackages/client-runtime/src/state/threadCommands.ts
@saphidsaphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837eCompareAugust 13, 2026 23:24
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Note

🤖 Claude Fable 5.1 responding on behalf of Theo

Closing in favor of #6662, which is the same feature carried further.

Both PRs move thread read state into a server-owned projection column with mark-viewed and mark-unread commands behind a capability flag. #6662 also has, and this branch does not:

  • viewedThrough capping and rejection of malformed timestamps in the decider
  • Agent awareness relay suppression for viewedAt-only meta updates
  • Optimistic pending state on web that reconciles after a rejected or failed command
  • A focus-latched acknowledger so an explicit mark-unread survives alt-tab
  • Mobile acks gated on route focus, app state, and connection
  • A dedicated client scheduler so view writes never block turn or archive commands

This branch is 472 commits behind main with 21 conflicting files, and its migration 041 now collides with 041_AuthSessionClientConnection on main. Rebasing it would cost more than the delta is worth. #6662 has been through two full review passes and only needs a rebase and a migration renumber to 043.

@t3dotggt3dotgg closed this Sep 1, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR 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

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

coderabbitaiBot 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-actionsgithub-actionsBot 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 threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/server/src/orchestration/decider.ts
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actionsBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB+1 B (+0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+16 B (+0.3%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−15 B (−0.2%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.3 KiB11.3 KiB+31 B (+0.3%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB+19 B (+0.3%)7.3 KiB
ClaudeLive turn WebSocket wire5.8 KiB5.8 KiB+12 B (+0.2%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (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

macroscopeappBot 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
t3dotggforce-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1eCompareAugust 8, 2026 08:01
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadapps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1CompareAugust 8, 2026 08:07
Comment threadapps/server/src/orchestration/decider.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededdaCompareAugust 8, 2026 08:10
Comment threadapps/web/src/components/threadActionMenu.logic.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466dCompareAugust 8, 2026 08:25
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259CompareAugust 8, 2026 08:35
Comment threadapps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0CompareAugust 8, 2026 08:39
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadpackages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94CompareAugust 8, 2026 08:49
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Terrible when compared to #5652

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6fCompareAugust 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.

🟡 Mediumsrc/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.

@cursorcursorBot 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 threadpackages/client-runtime/src/state/threadCommands.ts
@saphidsaphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837eCompareAugust 13, 2026 23:24
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Note

🤖 Claude Fable 5.1 responding on behalf of Theo

Closing in favor of #6662, which is the same feature carried further.

Both PRs move thread read state into a server-owned projection column with mark-viewed and mark-unread commands behind a capability flag. #6662 also has, and this branch does not:

  • viewedThrough capping and rejection of malformed timestamps in the decider
  • Agent awareness relay suppression for viewedAt-only meta updates
  • Optimistic pending state on web that reconciles after a rejected or failed command
  • A focus-latched acknowledger so an explicit mark-unread survives alt-tab
  • Mobile acks gated on route focus, app state, and connection
  • A dedicated client scheduler so view writes never block turn or archive commands

This branch is 472 commits behind main with 21 conflicting files, and its migration 041 now collides with 041_AuthSessionClientConnection on main. Rebasing it would cost more than the delta is worth. #6662 has been through two full review passes and only needs a rebase and a migration renumber to 043.

@t3dotggt3dotgg closed this Sep 1, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR 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

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

coderabbitaiBot 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-actionsgithub-actionsBot 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 threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/server/src/orchestration/decider.ts
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actionsBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB+1 B (+0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+16 B (+0.3%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−15 B (−0.2%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.3 KiB11.3 KiB+31 B (+0.3%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB+19 B (+0.3%)7.3 KiB
ClaudeLive turn WebSocket wire5.8 KiB5.8 KiB+12 B (+0.2%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (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

macroscopeappBot 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
t3dotggforce-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1eCompareAugust 8, 2026 08:01
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadapps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1CompareAugust 8, 2026 08:07
Comment threadapps/server/src/orchestration/decider.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededdaCompareAugust 8, 2026 08:10
Comment threadapps/web/src/components/threadActionMenu.logic.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466dCompareAugust 8, 2026 08:25
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259CompareAugust 8, 2026 08:35
Comment threadapps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0CompareAugust 8, 2026 08:39
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadpackages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94CompareAugust 8, 2026 08:49
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Terrible when compared to #5652

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6fCompareAugust 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.

🟡 Mediumsrc/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.

@cursorcursorBot 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 threadpackages/client-runtime/src/state/threadCommands.ts
@saphidsaphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837eCompareAugust 13, 2026 23:24
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Note

🤖 Claude Fable 5.1 responding on behalf of Theo

Closing in favor of #6662, which is the same feature carried further.

Both PRs move thread read state into a server-owned projection column with mark-viewed and mark-unread commands behind a capability flag. #6662 also has, and this branch does not:

  • viewedThrough capping and rejection of malformed timestamps in the decider
  • Agent awareness relay suppression for viewedAt-only meta updates
  • Optimistic pending state on web that reconciles after a rejected or failed command
  • A focus-latched acknowledger so an explicit mark-unread survives alt-tab
  • Mobile acks gated on route focus, app state, and connection
  • A dedicated client scheduler so view writes never block turn or archive commands

This branch is 472 commits behind main with 21 conflicting files, and its migration 041 now collides with 041_AuthSessionClientConnection on main. Rebasing it would cost more than the delta is worth. #6662 has been through two full review passes and only needs a rebase and a migration renumber to 043.

@t3dotggt3dotgg closed this Sep 1, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR 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

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

coderabbitaiBot 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-actionsgithub-actionsBot 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 threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/server/src/orchestration/decider.ts
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actionsBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB+1 B (+0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+16 B (+0.3%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−15 B (−0.2%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.3 KiB11.3 KiB+31 B (+0.3%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB+19 B (+0.3%)7.3 KiB
ClaudeLive turn WebSocket wire5.8 KiB5.8 KiB+12 B (+0.2%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (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

macroscopeappBot 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
t3dotggforce-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1eCompareAugust 8, 2026 08:01
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadapps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1CompareAugust 8, 2026 08:07
Comment threadapps/server/src/orchestration/decider.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededdaCompareAugust 8, 2026 08:10
Comment threadapps/web/src/components/threadActionMenu.logic.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466dCompareAugust 8, 2026 08:25
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259CompareAugust 8, 2026 08:35
Comment threadapps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0CompareAugust 8, 2026 08:39
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadpackages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94CompareAugust 8, 2026 08:49
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Terrible when compared to #5652

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6fCompareAugust 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.

🟡 Mediumsrc/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.

@cursorcursorBot 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 threadpackages/client-runtime/src/state/threadCommands.ts
@saphidsaphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837eCompareAugust 13, 2026 23:24
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Note

🤖 Claude Fable 5.1 responding on behalf of Theo

Closing in favor of #6662, which is the same feature carried further.

Both PRs move thread read state into a server-owned projection column with mark-viewed and mark-unread commands behind a capability flag. #6662 also has, and this branch does not:

  • viewedThrough capping and rejection of malformed timestamps in the decider
  • Agent awareness relay suppression for viewedAt-only meta updates
  • Optimistic pending state on web that reconciles after a rejected or failed command
  • A focus-latched acknowledger so an explicit mark-unread survives alt-tab
  • Mobile acks gated on route focus, app state, and connection
  • A dedicated client scheduler so view writes never block turn or archive commands

This branch is 472 commits behind main with 21 conflicting files, and its migration 041 now collides with 041_AuthSessionClientConnection on main. Rebasing it would cost more than the delta is worth. #6662 has been through two full review passes and only needs a rebase and a migration renumber to 043.

@t3dotggt3dotgg closed this Sep 1, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR 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

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

coderabbitaiBot 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-actionsgithub-actionsBot 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 threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/server/src/orchestration/decider.ts
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actionsBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB+1 B (+0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+16 B (+0.3%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−15 B (−0.2%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.3 KiB11.3 KiB+31 B (+0.3%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB+19 B (+0.3%)7.3 KiB
ClaudeLive turn WebSocket wire5.8 KiB5.8 KiB+12 B (+0.2%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (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

macroscopeappBot 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
t3dotggforce-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1eCompareAugust 8, 2026 08:01
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadapps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1CompareAugust 8, 2026 08:07
Comment threadapps/server/src/orchestration/decider.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededdaCompareAugust 8, 2026 08:10
Comment threadapps/web/src/components/threadActionMenu.logic.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466dCompareAugust 8, 2026 08:25
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259CompareAugust 8, 2026 08:35
Comment threadapps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0CompareAugust 8, 2026 08:39
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadpackages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94CompareAugust 8, 2026 08:49
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Terrible when compared to #5652

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6fCompareAugust 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.

🟡 Mediumsrc/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.

@cursorcursorBot 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 threadpackages/client-runtime/src/state/threadCommands.ts
@saphidsaphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837eCompareAugust 13, 2026 23:24
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Note

🤖 Claude Fable 5.1 responding on behalf of Theo

Closing in favor of #6662, which is the same feature carried further.

Both PRs move thread read state into a server-owned projection column with mark-viewed and mark-unread commands behind a capability flag. #6662 also has, and this branch does not:

  • viewedThrough capping and rejection of malformed timestamps in the decider
  • Agent awareness relay suppression for viewedAt-only meta updates
  • Optimistic pending state on web that reconciles after a rejected or failed command
  • A focus-latched acknowledger so an explicit mark-unread survives alt-tab
  • Mobile acks gated on route focus, app state, and connection
  • A dedicated client scheduler so view writes never block turn or archive commands

This branch is 472 commits behind main with 21 conflicting files, and its migration 041 now collides with 041_AuthSessionClientConnection on main. Rebasing it would cost more than the delta is worth. #6662 has been through two full review passes and only needs a rebase and a migration renumber to 043.

@t3dotggt3dotgg closed this Sep 1, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR 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

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

coderabbitaiBot 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-actionsgithub-actionsBot 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 threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/server/src/orchestration/decider.ts
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actionsBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB+1 B (+0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+16 B (+0.3%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−15 B (−0.2%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.3 KiB11.3 KiB+31 B (+0.3%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB+19 B (+0.3%)7.3 KiB
ClaudeLive turn WebSocket wire5.8 KiB5.8 KiB+12 B (+0.2%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (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

macroscopeappBot 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
t3dotggforce-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1eCompareAugust 8, 2026 08:01
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadapps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1CompareAugust 8, 2026 08:07
Comment threadapps/server/src/orchestration/decider.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededdaCompareAugust 8, 2026 08:10
Comment threadapps/web/src/components/threadActionMenu.logic.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466dCompareAugust 8, 2026 08:25
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259CompareAugust 8, 2026 08:35
Comment threadapps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0CompareAugust 8, 2026 08:39
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadpackages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94CompareAugust 8, 2026 08:49
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Terrible when compared to #5652

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6fCompareAugust 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.

🟡 Mediumsrc/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.

@cursorcursorBot 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 threadpackages/client-runtime/src/state/threadCommands.ts
@saphidsaphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837eCompareAugust 13, 2026 23:24
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Note

🤖 Claude Fable 5.1 responding on behalf of Theo

Closing in favor of #6662, which is the same feature carried further.

Both PRs move thread read state into a server-owned projection column with mark-viewed and mark-unread commands behind a capability flag. #6662 also has, and this branch does not:

  • viewedThrough capping and rejection of malformed timestamps in the decider
  • Agent awareness relay suppression for viewedAt-only meta updates
  • Optimistic pending state on web that reconciles after a rejected or failed command
  • A focus-latched acknowledger so an explicit mark-unread survives alt-tab
  • Mobile acks gated on route focus, app state, and connection
  • A dedicated client scheduler so view writes never block turn or archive commands

This branch is 472 commits behind main with 21 conflicting files, and its migration 041 now collides with 041_AuthSessionClientConnection on main. Rebasing it would cost more than the delta is worth. #6662 has been through two full review passes and only needs a rebase and a migration renumber to 043.

@t3dotggt3dotgg closed this Sep 1, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR 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

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

coderabbitaiBot 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-actionsgithub-actionsBot 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 threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/server/src/orchestration/decider.ts
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actionsBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB+1 B (+0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+16 B (+0.3%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−15 B (−0.2%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.3 KiB11.3 KiB+31 B (+0.3%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB+19 B (+0.3%)7.3 KiB
ClaudeLive turn WebSocket wire5.8 KiB5.8 KiB+12 B (+0.2%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (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

macroscopeappBot 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
t3dotggforce-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1eCompareAugust 8, 2026 08:01
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadapps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1CompareAugust 8, 2026 08:07
Comment threadapps/server/src/orchestration/decider.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededdaCompareAugust 8, 2026 08:10
Comment threadapps/web/src/components/threadActionMenu.logic.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466dCompareAugust 8, 2026 08:25
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259CompareAugust 8, 2026 08:35
Comment threadapps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0CompareAugust 8, 2026 08:39
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadpackages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94CompareAugust 8, 2026 08:49
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Terrible when compared to #5652

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6fCompareAugust 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.

🟡 Mediumsrc/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.

@cursorcursorBot 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 threadpackages/client-runtime/src/state/threadCommands.ts
@saphidsaphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837eCompareAugust 13, 2026 23:24
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Note

🤖 Claude Fable 5.1 responding on behalf of Theo

Closing in favor of #6662, which is the same feature carried further.

Both PRs move thread read state into a server-owned projection column with mark-viewed and mark-unread commands behind a capability flag. #6662 also has, and this branch does not:

  • viewedThrough capping and rejection of malformed timestamps in the decider
  • Agent awareness relay suppression for viewedAt-only meta updates
  • Optimistic pending state on web that reconciles after a rejected or failed command
  • A focus-latched acknowledger so an explicit mark-unread survives alt-tab
  • Mobile acks gated on route focus, app state, and connection
  • A dedicated client scheduler so view writes never block turn or archive commands

This branch is 472 commits behind main with 21 conflicting files, and its migration 041 now collides with 041_AuthSessionClientConnection on main. Rebasing it would cost more than the delta is worth. #6662 has been through two full review passes and only needs a rebase and a migration renumber to 043.

@t3dotggt3dotgg closed this Sep 1, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR 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

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

coderabbitaiBot 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-actionsgithub-actionsBot 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 threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx Outdated
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
Comment threadapps/server/src/orchestration/decider.ts
Comment threadapps/web/src/components/SidebarV2.tsx Outdated
@github-actions

github-actionsBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB+1 B (+0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+16 B (+0.3%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−15 B (−0.2%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.3 KiB11.3 KiB+31 B (+0.3%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB+19 B (+0.3%)7.3 KiB
ClaudeLive turn WebSocket wire5.8 KiB5.8 KiB+12 B (+0.2%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (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

macroscopeappBot 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
t3dotggforce-pushed the t3code/server-side-thread-status branch from 9b9ebb8 to 92bcd1eCompareAugust 8, 2026 08:01
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadapps/web/src/components/Sidebar.logic.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 92bcd1e to 1ec6fd1CompareAugust 8, 2026 08:07
Comment threadapps/server/src/orchestration/decider.ts
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 1ec6fd1 to fededdaCompareAugust 8, 2026 08:10
Comment threadapps/web/src/components/threadActionMenu.logic.ts Outdated
Comment threadapps/web/src/components/ChatView.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch 2 times, most recently from d3a0393 to 6f0466dCompareAugust 8, 2026 08:25
Comment threadapps/web/src/components/ChatView.tsx
Comment threadapps/mobile/src/features/threads/ThreadRouteScreen.tsx
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 6f0466d to 4a75259CompareAugust 8, 2026 08:35
Comment threadapps/web/src/components/ChatView.tsx Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 4a75259 to f3e1ea0CompareAugust 8, 2026 08:39
Comment threadapps/web/src/hooks/useThreadActions.ts
Comment threadpackages/client-runtime/src/state/threadCommands.ts Outdated
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f3e1ea0 to f08fd94CompareAugust 8, 2026 08:49
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Terrible when compared to #5652

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Wrong pr my bad, too many tabs lol

@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from f08fd94 to 0125c6fCompareAugust 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.

🟡 Mediumsrc/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.

@cursorcursorBot 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 threadpackages/client-runtime/src/state/threadCommands.ts
@saphidsaphid mentioned this pull request Aug 8, 2026
5 tasks
@t3dotgg
t3dotggforce-pushed the t3code/server-side-thread-status branch from 0125c6f to 19b837eCompareAugust 13, 2026 23:24
@t3dotgg

Copy link
Copy Markdown
MemberAuthor

Note

🤖 Claude Fable 5.1 responding on behalf of Theo

Closing in favor of #6662, which is the same feature carried further.

Both PRs move thread read state into a server-owned projection column with mark-viewed and mark-unread commands behind a capability flag. #6662 also has, and this branch does not:

  • viewedThrough capping and rejection of malformed timestamps in the decider
  • Agent awareness relay suppression for viewedAt-only meta updates
  • Optimistic pending state on web that reconciles after a rejected or failed command
  • A focus-latched acknowledger so an explicit mark-unread survives alt-tab
  • Mobile acks gated on route focus, app state, and connection
  • A dedicated client scheduler so view writes never block turn or archive commands

This branch is 472 commits behind main with 21 conflicting files, and its migration 041 now collides with 041_AuthSessionClientConnection on main. Rebasing it would cost more than the delta is worth. #6662 has been through two full review passes and only needs a rebase and a migration renumber to 043.

@t3dotggt3dotgg closed this Sep 1, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL500-999 changed lines (additions + deletions).vouch:trustedPR 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

@t3dotgg