Conversation
- Cache EnvironmentStore's decoded document; publishes read it several times per second and previously hit disk + JSON decode every time - Memoize parsed ISO8601 dates in NativeFeatureClient; every publish re-parsed ~10 timestamps per thread through two formatters - Cache the mapped provider catalog per environment, invalidated on server config writes, instead of rebuilding hundreds of structs per publish - Sort activity logs by raw ISO string instead of parsing dates inside the comparator (O(n log n) formatter calls per detail rebuild) - Skip attachment hydration entirely for text-only threads - Dedupe emitConnection so per-event yields only publish transitions - Early-out acknowledgeDeliveredMessages when the outbox is empty - Cap terminal buffers at 256KB; unbounded append could OOM on verbose commands and re-layout megabyte strings per chunk - Key the markdown document cache by content fingerprint instead of the whole source string, stop inserting streaming intermediates (which churned completed messages out), and promote the final streamed render on completion instead of reparsing on the main thread - Throttle streaming markdown renders (150ms leading-edge) instead of a 200ms trailing debounce that never fired at an 80ms publish cadence, which left streaming messages as plain text for whole turns - Share JSONEncoder/Decoder.t3 instances (were rebuilt per call) and skip sortedKeys on throwaway JSONValue bridging encodes - Weak-capture the RPC connection loop and keepalive so released clients can actually deinit; snapshot dictionary keys before reentrant iteration in connected(); scope subscription Interrupts to the connection that assigned the request ID; add reconnect jitter - Cache DPoP JWK + thumbprint (SHA-256 per managed request before) and the proof encoder; reuse ISO8601 formatter in command builders - Replace crash-prone Dictionary(uniqueKeysWithValues:) on server-supplied IDs with first-wins reduces (model picker, agent awareness); retain per-thread states instead of full FeatureThread copies for transition signals; idle the home list timer to 60s when nothing is working Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Move table column-width estimation into the background render pass and store it on MarkdownRenderedTable; the view measured every cell's AttributedString per body evaluation on the main thread - Make rendered markdown blocks Equatable (inline runs compare by reference thanks to the streaming run cache) and wrap block views in .equatable(), so only the changed tail of a streaming message re-renders instead of every block - Gate the thread header's per-second TimelineView on working status; idle threads render a static status - Decode local attachment previews off-main through the shared thumbnail cache; UIImage(data:) ran in body per reconfigure - Memoize composer trigger parsing (was parsed 4x per keystroke) - Drop markdown caches on memory warnings - Stop the QR capture session on viewDidDisappear/backgrounding and resume on reappear; dismantle was the only stop path - Retry pending workspace navigation requests when snapshot data lands so cold-start deep links are not silently dropped - Scope approval/user-input resolution to details that contain the request instead of deep-comparing every cached transcript - Binary-search diff hydration anchors (was O(n^2) on large files) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Move streaming markdown rendering out of .task(id:) into a renderer that survives revision changes; the per-revision task cancelled any in-flight parse, so messages that parse slower than the publish cadence never rendered. One render runs at a time, latest revision wins, 150ms throttle. Keep showing the previous streamed document between renders instead of flashing back to plain text. - Cap the terminal buffer in UTF-8 bytes (the unit the limit is defined in) instead of Character count, snapping to a character boundary. - Start the home list timer after items are populated so an initially working thread gets the 1Hz interval; re-evaluate on every update. - Sort activities by memoized parsed dates (stable, wire-order ties); raw ISO strings sort wrong across mixed fractional representations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A cancelled drain unwinding after a replacement started could clear the replacement's task slot, allowing two concurrent drains to deliver out of order. Generation-stamp each drain; only the current generation may clear the shared slot, loop, or deliver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stale-document fallback kept any revision-mismatched render while streaming, so a reused transcript cell whose @State survived recycling could briefly show another message's markdown. Only accept a stale document whose source is a prefix of the current message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9543d11 to
220aa67
Compare
220aa67 to
5115caa
Compare
A detached accepted-command refresh could finish after deleteThread and publish the deleted thread again. Cancel it once the server accepts the delete. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ng older state A detail read that started before a newer send was accepted could publish after it and hide the newer message until the next read. Skip publishing a superseded read (its refresh loop reads again), and stop a cancelled shell refresh from writing a shell read before the change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ives A Stop accepted during a send's detail read discarded that read without requesting another, and an older detail could replace the transcript before the accepted message reached it. Re-read detail after a discarded read, and keep delivered messages until a server transcript includes them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Do not retain a delivered message that a server transcript already confirmed, keep only its display copy, and drop retained messages when their thread details are removed or cleared. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Dismissing prior approval to re-evaluate 302ea89
Evicting a cached thread detail forgot its accepted messages, so a stale reopen hid them. A restored outbox or a new task's local detail was also recorded as the server transcript, so its accepted prompt was treated as confirmed and disappeared on the next older read. Only server transcripts now confirm delivery, and eviction keeps retained messages. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A newer send can be delivered while an older one waits to retry. Delivered copies were placed before every queued copy, which reversed them. Merge both by send time. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@t3dotgg this is ready for your review and merge. Merge order: stacked chain, merge in order: #10758 → #10759 → #10761 → #10762 → #10763 → #10764 → #10765 → #10766 → #10767. #12655 (composer focus freeze) is independent and can merge at any time. Evidence at head
Independent review: GPT-6 Astra xhigh (read-only) reviewed this layer in four rounds and returned ready to merge in round 4. It then re-checked the later fixes: the eviction/local-detail fix (one medium found and fixed, re-check ready) and the send-order fix (no issues). Macroscope findings on the latest heads have been verified against the source: real ones are fixed, and the rest have a written reason and are listed under Follow-ups in the description. |
1637f70 to
cf79028
Compare
Review this layer
This is the first PR in the chain, so the diff GitHub shows here is this layer alone.
This layer's diff: +992 / −29 lines across 4 files; app code +150 / −24, the rest is tests and docs.
Accepted Send and Stop commands keep their UI actions waiting for optional HTTP reads. Release the accepted action immediately and let a generation-owned refresh reconcile the thread separately. Coalesce overlapping Send/Stop refresh requirements without replaying the command.
Pre-restack CI verification: GitHub checks passed on the original head, including contract fixtures and native tests. Skipped checks are not claimed as executed proof.
Before this restack, all 12 standalone command-acknowledgement/retry XCTest cases passed (exit 0). The old-environment late-ack regression relies on the later live-bootstrap connection lifetime and remains covered in the dependent chain.
Delivery: first of the stacked thread-sync chain (#10758 → #10759 → #10761 → #10762 → #10763 → #10764 → #10765 → #10766 → #14021 → #10767); merges on its own.
Base: Theo’s
t3code/rebuild-mobile-app-swift,157476f1fb.Historical verification scope (before this restack): Native tests passed with optional HTTP reads held while accepted Send/Stop completes. They verify outbox completion, refresh coalescing, cancellation on close/disconnect, and absence of command replay. The direct PR excludes a test requiring the later passive-live lifetime; that test remains in the dependent chain. The native CI job linked above contains the passing receipts. The tests exercise the protocol, state, or accessibility-value boundary changed here.
Refresh onto
157476f1fb(2026-09-27)Rebased with the thread-sync chain onto the current
t3code/rebuild-mobile-app-swift(157476f1fb). Head93fe314002.NativeRetryIdentityTests14 passed; before the delete fix,NativeMultiEnvironmentTests,NativeThreadCatchUpTests,NativeRetryIdentityTestsandFeatureRootModelTestspassed 78 tests.T3CodeTestsat the chain top (68d92c2752): 343 XCTest cases passed (1 skipped). Swift Testing ran 892 tests; the only 2 failures areUsageModelsTestscurrency formatting, which fail identically on unmodified157476f1fb.devin -p --sandbox --permission-mode smart --model swe-2-maxover the whole chain diff, exit 0. No blocker, high or medium findings. A dead-state finding was fixed. A report that Stop state outlives a vanished thread was not changed, becausestopSurvivesCloseReopenAndMissingReconnectSeedrequires that behaviour.deleteThreadnow cancels it once the server accepts the delete (testDeletingThreadCancelsItsAcceptedSendRefresh; without the fix the test hangs).NativeRetryIdentityTests: 14 passed.Review fixes (rounds 2–4)
testSupersededAcceptedSendRefreshDoesNotPublishItsOlderDetail(fails without the fix).deliveredMessageSurvivesAnOlderDetailUntilTheServerIncludesIt.confirmedDeliveredMessageDoesNotReturnAfterARewind,clearedDetailsDropRetainedDeliveredMessages.Review fixes (round 6: Macroscope and Astra)
evictedDetailKeepsItsDeliveredMessageOnReopen(fails without the fix).startedTaskKeepsItsPromptUntilTheServerIncludesIt(fails without the fix).Review fixes (round 7: Macroscope and Astra)
localCopiesKeepSendOrderWhenANewerSendIsDeliveredFirst(fails without the fix). Astra found no issues with this change.Independent review
GPT-6 Astra (
codex exec -m gpt-6-astra -c model_reasoning_effort="xhigh" --sandbox read-only) reviewed this layer four times. Rounds 1–3 found the defects fixed above. Round 4 returned ready to merge.Follow-ups (minor, not blocking)
Reproduction
iOS 27 Simulator, Debug builds, paired to a disposable local server through a fault-injecting proxy that never answers
GET /api/orchestration/threads/*or/api/orchestration/shell, like a stalled mobile connection. Commands still go through. Both builds include #12655 so the composer can be focused. Send one message, then a second.157476f1fb): the first send holds the composer until its refresh reads time out (8 s thread read, then 61 s shell read). The second message's send button stays disabled for about 70 s.GIFs are 4× speed. Real-time recordings: before.mp4 · after.mp4.
Model and harness: GPT-6 / Codex. Rebase onto
157476f1fb: Claude Opus 5.5 / Claude Code.