perf(mobile): sends respond instantly, thread opens stop freezing - #4882
Conversation
Sending a message did nothing for seconds because the queued state waited on a durable file write, and thread opens stalled on quadratic feed grouping plus duplicated activity sorts. New-thread froze mid-sheet while native autoFocus forced the keyboard up during the present animation. - Publish outbox enqueues to the atom synchronously, write behind, roll back on failure; clear the composer draft on the tap frame - Arm the Live Activity after the send instead of before it - Make groupAdjacentActivities linear; sort activities once for approvals + user inputs instead of twice - Focus the new-task composer after interactions (iOS) instead of via native autoFocus during the transition - Memoize the v2 thread list's FlatList extraData - Host useThreadOutboxDrain in a null-rendering leaf so outbox/shell updates stop re-rendering the root navigation layout - Skip the active-thread shell subscription on iOS in useAppShortcuts (Android-only feature) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0b7c9d8. Configure here.
ApprovabilityVerdict: Needs human review This PR changes the timing and ordering semantics of message enqueueing with an optimistic publish + rollback pattern, and adds complex race condition handling in the drain logic. While performance-focused and well-tested, these architectural changes to message queuing behavior warrant human review. You can customize Macroscope's approvability policy. Learn more. |
Review findings on the optimistic outbox: - Roll back a failed enqueue by reference, not messageId, so a same-id retry that replaced the attempt survives the first attempt's failure - Drain confirms pending writes settled (confirmQueued) before dispatching, so a message whose write later fails can never have already been delivered - Failure rollback restores attachments via the uncapped append instead of the truncating merge path Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion The drain's threadBusy and editing-state guards were evaluated before awaiting write confirmation, so a thread going busy or the user opening the message in the editor during a slow write could still dispatch a stale payload. Re-read both from the registry after the await and defer to the next drain pass instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What's Changed * fix(connect): suggest a serve command that matches how you ran connect by @t3dotgg in pingdotgg/t3code#4897 * fix(mobile): stop shared content errors in Personal Team builds by @t3dotgg in pingdotgg/t3code#4943 * perf(mobile): sends respond instantly, thread opens stop freezing by @t3dotgg in pingdotgg/t3code#4882 * fix(web): show Codex fast mode as a bolt by @t3dotgg in pingdotgg/t3code#4947 * docs: seed worktrees with a copy of real userdata instead of banning it by @t3dotgg in pingdotgg/t3code#4949 **Full Changelog**: pingdotgg/t3code@v0.0.32-nightly.20260730.954...v0.0.32-nightly.20260730.955 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.32-nightly.20260730.955
* fix(desktop): restore T3 Connect sign-in (pingdotgg#4809) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Simplify files panel header (pingdotgg#4828) * build(desktop): reduce installed app size by ~300MB (pingdotgg#4824) * Update model version from claude-opus-4-8 to claude-opus-5 (pingdotgg#4832) * Preserve the thread shell while detail loads (pingdotgg#4830) * Reduce idle work and disk churn with native resource diagnostics (pingdotgg#2679) Co-authored-by: codex <codex@users.noreply.github.com> * fix(server): detect repositories after initialization (pingdotgg#4848) * perf(server): merge separate staged/unstaged numstat calls into single diff HEAD --numstat (pingdotgg#4843) * fix(git): disable external diff for review diff previews (pingdotgg#4854) * Fix editable file focus and live syntax highlighting (pingdotgg#3979) * fix(web): remember the rendered-markdown choice across threads (pingdotgg#4853) Co-authored-by: Simon Doba <simon.doba@orbit.de> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * chore(release): prepare v0.0.31 * fix(mobile): reduce thread feed scroll jank (pingdotgg#4874) * fix(web): restore sidebar v2 thread actions and terminal icon (pingdotgg#4712) * fix(web): settle button now works on hover, not just right-click (pingdotgg#4905) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(clients): disable add project while disconnected (pingdotgg#4834) * fix(composer): hide default Codex service tier (pingdotgg#4784) * docs: link iOS and Android app store downloads (pingdotgg#4902) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): align remote server update action (pingdotgg#4731) * fix(connect): suggest a serve command that matches how you ran connect (pingdotgg#4897) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(mobile): stop shared content errors in Personal Team builds (pingdotgg#4943) * perf(mobile): sends respond instantly, thread opens stop freezing (pingdotgg#4882) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): show Codex fast mode as a bolt (pingdotgg#4947) * docs: seed worktrees with a copy of real userdata instead of banning it (pingdotgg#4949) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(mobile): support dragged images in the composer (pingdotgg#4953) * fix(mobile): stop long iOS threads from jumping while scrolling up (pingdotgg#4867) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): keep worktree default when switching a draft's machine (pingdotgg#4964) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * perf(mobile): reconnect environments immediately on resume (pingdotgg#4878) * feat(web): pasting a huge screenshot now compresses it instead of erroring (pingdotgg#4967) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat(web): regenerate thread titles from sidebar (pingdotgg#4810) * fix(web): show server update progress through reconnect (pingdotgg#4903) --------- Co-authored-by: Alex <me@pixp.cc> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: wukko <me@wukko.me> Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> Co-authored-by: ohbentos <72638975+ohbentos@users.noreply.github.com> Co-authored-by: Jake Leventhal <jakeleventhal@me.com> Co-authored-by: Simon Doba <simon.doba@hotmail.de> Co-authored-by: Simon Doba <simon.doba@orbit.de> Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: Gabriel De Andrade <30420087+gabrielelpidio@users.noreply.github.com> Co-authored-by: Jono Kemball <Noojuno@users.noreply.github.com> Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Max Katz <me@maxkatz.me>

On iOS, sending a message did nothing for 3+ seconds before showing "queued", opening a thread locked the app for ~1s before the transition, and starting a new thread froze before the composer appeared. A multi-agent investigation traced each symptom; this PR takes the highest-impact fixes that were safe to make small.
Send: feedback on the tap frame. The "queued" state waited on a durable JSON file write (with attachments inlined as base64) before anything changed on screen, and the draft only cleared after that resolved. The outbox atom now publishes synchronously with the write happening behind it (rolled back on failure, content restored to the draft), and the lock-screen Live Activity arms after the send instead of before. Sending now clears the composer and shows "queued" immediately; the file write and relay round trip happen behind that feedback.
Thread open: linear feed derivation.
groupAdjacentActivitiescopied the accumulated group array per activity — O(k²) on long tool runs. It now appends into a mutable trailing group. Isolated benchmark: 2,000 activities in one turn drops 1.47ms → 0.009ms per pass (~160x); at 5,000 activities ~1.8ms → 0.03ms. This runs on every thread open and every streaming update, and pending approvals/user inputs re-sorted the full activity array twice more per update — now sorted once and shared.New thread: keyboard no longer races the sheet. The native composer's
autoFocusfiredbecomeFirstResponderthe moment the view attached, forcing iOS keyboard bring-up (300ms+ cold) during the formSheet present animation. The existingInteractionManager-deferred focus takes over, so the sheet animates immediately and the keyboard rises after it settles.Re-render hygiene. The v2 home thread list passed a fresh
extraDataobject literal every render, forcing all visible FlatList rows to re-render on any HomeScreen render — now memoized.useThreadOutboxDrain(subscribed to all threads, projects, connections, and the outbox) ran directly in the root navigation layout, re-rendering every screen on each enqueue/shell/connection change — now hosted in a null-rendering leaf. The Android-only shortcuts hook also subscribed the iOS root to the active thread shell — now gated.Estimated impact (release builds; ranked by user-visible effect):
Deeper follow-ups identified but out of scope here: client-side event-stream batching, using the existing granular thread-detail slice atoms, skipping re-decode of warm snapshots, dropping the drain's redundant pre-flight settings RPCs, and file-referenced (not base64) attachments.
Written by Claude Fable 5 in Claude Code.
🤖 Generated with Claude Code
Note
Medium Risk
Optimistic outbox + confirm-before-drain changes message delivery ordering and failure recovery; incorrect rollback or stale guard logic could drop or duplicate sends, though new tests cover write failure and same-id retry cases.
Overview
Makes send feel immediate and cuts main-thread work and root re-renders that were freezing thread open and new task flows.
Thread outbox: Queued messages update the in-memory atom before the durable write finishes; the composer clears on the tap frame. Failed writes roll back the queue entry and merge text back into the draft (attachments via uncapped append). The drain calls
confirmThreadOutboxMessageQueuedand re-checks edit/busy state after that await so rolled-back or stale messages are never delivered.Re-render isolation:
useThreadOutboxDrainmoves into a nullThreadOutboxDrainWorkerso outbox/shell/connection churn does not re-renderRootStackLayout. On iOS, launcher shortcut sync no longer subscribes to the active thread shell. Home v2FlatListextraDatais memoized.Feed derivation:
groupAdjacentActivitiesappends into a mutable trailing group (avoids quadratic copies).sortThreadActivitiesruns once and feeds both pending-approval and pending-user-input derivations.UX timing: New-task composer
autoFocusis off on iOS; focus stays deferred until after the sheet animation. Live Activity arms afteronSendMessagecompletes.Reviewed by Cursor Bugbot for commit 1d87beb. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Make mobile send respond instantly and stop thread opens from freezing
confirmQueuedAPI to verify the durable write succeeded before dispatching, preventing dispatch of rolled-back or stale messages.ThreadOutboxDrainWorkerin Stack.tsx isolates outbox drain subscriptions to a leaf component, preventing outbox state updates from re-renderingRootStackLayout.ComposerEditorin NewTaskDraftScreen.tsx defers auto-focus until after the iOS sheet presentation animation completes.FlatList.extraDatain HomeScreen.tsx is memoized to avoid unnecessary row re-renders, anduseRecentThreadShortcutSyncin useAppShortcuts.ts skips active thread subscription on iOS.onSendMessagenow resolves immediately with the newmessageIdwithout waiting for the durable write; callers previously relying on post-send resolution timing may see different behavior.Macroscope summarized 1d87beb.