Skip to content

Port upstream thread-performance improvements (top 10 + fixes) - #317

Merged
SergeSerb2 merged 9 commits into
mainfrom
sergecode/thread-perf-port
Jul 29, 2026
Merged

Port upstream thread-performance improvements (top 10 + fixes)#317
SergeSerb2 merged 9 commits into
mainfrom
sergecode/thread-perf-port

Conversation

@SergeSerb2

Copy link
Copy Markdown
Owner

Ports the full top-10 list from plans/upstream-thread-perf-catchup.md (PR #316) — the thread-performance and thread-data improvements upstream t3code landed after our fork point — plus the bonus image-path fix. All upstream refs are t3code PR numbers.

Server transport

  • Gzip large JSON HTTP responses (#4788/#4798): global router middleware, ≥1 KB application/json bodies, full Accept-Encoding q-value parsing, Vary handling; native node:zlib on Node / CompressionStream on Bun behind an HttpResponseCompression service. Thread/shell snapshot endpoints now compress (~10x on large threads).
  • WebSocket permessage-deflate (#4705): patches @effect/platform-node/-bun @ beta.78 to negotiate compression (Node keeps context takeover; Bun uses dedicated compress + shared decompress to dodge uWebSockets close-1006). Ships natively in platform ≥ beta.103 — drop the patches on the next Effect upgrade.

Server data volume

  • Context-window row trimming (#4791): detail snapshots keep only the last resolvable context-window.updated activity per turn — long threads previously shipped thousands of rows clients never read.
  • Image-path preservation in payload pruning (upstream 0a9ea4bbe): fixes a live bug where pruned image view/generation activities lost their type+path and chat galleries rendered blank; base64 bytes stay pruned.
  • Shell stream coalescing + bounded catch-up (#4177): the shared shell firehose coalesces per aggregate over a 50 ms/512-event window (bounded refetch concurrency, upsert-or-remove correctness with retry); resume replay is capped at 1000 events with fallback to one fresh snapshot; OrchestrationEngine.latestSequence exposes the committed head.
  • replayEvents RPC retired (#4791): the handler collected an unbounded event tail into memory and no client calls it anymore — contract, handler, enrichment machinery, and client wrappers removed.

Mac client

  • afterSequence resume for shell and thread subscriptions: reconnects replay only missed events instead of re-downloading full snapshot frames; sequence-gated replay/live overlap; fresh opens and post-eviction reopens still take the full snapshot. New LiveBackendShellResumeTests.

Mobile

  • Deferred work-log serialization (#4607): rows expose canExpand + memoized getFullDetail()/getCopyText() thunks instead of eagerly JSON-stringifying every entry's tool data at feed build.
  • Settled-section paging (Thread List v2 pattern): 10 rows per project group initially, +25 per "show more", reset on environment/search change.

Git

  • Ref refresh storm fix (#4727): single-flighted, TTL-cached ref snapshots keyed by git-common-dir, invalidated by mutating operations; repository-path lookups coalesced; upstream-fetch backoff 30s→15min per remote instead of a flat 5s retry.

Thread snoozing (#4311, full stack)

Snooze is an overlay on the active lifecycle: thread.snooze carries a wake time, clients suppress the thread until snoozedUntil passes (timer wakes emit no event), and the decider wakes it on real activity. Contracts, decider/projector/persistence (migration 040), client-runtime commands + state, mac sidebar Snooze/Wake menu (presets: 1h / 3h / tomorrow 9 AM / a week), and mobile long-press menu + moon-icon row hint.

Verification

  • pnpm run verify --all green: vp check, typecheck, full TS test suite, mac Swift suite (735 tests incl. new resume/snooze coverage), lint:mobile.
  • Ported upstream test suites throughout: projection dedup/context-window tests, 7 subscribeShell coalescing/resume tests, decider.snoozed (10 tests), threadSnoozed (20 tests), GitVcsDriverCore storm tests (8), mobile lazy-feed + paging + snooze tests.

🤖 Generated with Claude Code

SergeSerb2 and others added 9 commits July 28, 2026 19:50
Ports three upstream t3code changes:
- gzip large JSON HTTP responses behind HttpResponseCompression (native
  node:zlib on Node, CompressionStream on Bun) with Accept-Encoding
  q-value parsing and Vary handling (upstream #4788, #4798)
- drop all but the last resolvable context-window activity per turn from
  thread detail snapshots; live events untouched (upstream #4791)
- preserve image view/generation item type and path while pruning base64
  image bytes from activity payloads (upstream 0a9ea4bbe)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports upstream t3code #4177:
- coalesce the shared shell firehose per aggregate over a 50ms/512-event
  window with bounded refetch concurrency, so bursts of streaming deltas
  collapse into one shell refetch and never serialize a new thread's
  thread.created behind per-event DB reads
- upsert-or-remove projection reads: a refetch that returns none emits a
  removal (with one retry on read failure) so coalescing a delete into a
  later event still drops the row on clients
- subscribeShell resume: cap catch-up replay at SHELL_RESUME_MAX_GAP
  (1000) events; beyond that (or a cursor ahead of the head) send one
  fresh snapshot instead of an unbounded replay, and bound the replay
  read to the head captured at subscribe time
- completion marker rides the live buffer so events observed during
  snapshot/replay are delivered before the synchronized signal
- OrchestrationEngine.latestSequence exposes the committed head sequence

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports upstream t3code #4705: patch @effect/platform-node and
@effect/platform-bun (4.0.0-beta.78) to enable permessage-deflate on the
WS server. Node keeps context takeover (shared compression window across
frames); Bun uses a dedicated compressor with the shared decompressor to
avoid uWebSockets' close-1006 on valid DEFLATE input (uWebSockets.js#633).
Clients that do not offer the extension still get uncompressed frames.
Upstreamed as Effect-TS/effect#6691 — ships natively in platform
>= 4.0.0-beta.103; drop the patches on the next Effect upgrade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mac client re-downloaded a full snapshot frame for the shell and for
every open thread on each reconnect. Adopt the resume protocol the server
already speaks (upstream t3code #3719/#4163 lineage):

- track lastShellSequence from shell snapshots/events and pass it as
  subscribeShell(afterSequence:); the server replays only missed shell
  events, emits removals for aggregates deleted while disconnected, and
  falls back to a fresh snapshot past its gap cap — which the existing
  snapshot reconcile path already handles
- sequence-gate live shell events (replay/live overlap on resume)
- resume subscribeThread with the tracked lastThreadSequence when the
  in-memory timeline state is still intact; fresh opens and post-eviction
  reopens still take the full snapshot so timeline() waiters resolve

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports two upstream t3code changes:
- thread feed rows no longer eagerly build expanded-body and copy text
  for every work-log entry (JSON.stringify of tool data per row); rows
  expose canExpand plus memoized getFullDetail()/getCopyText() thunks
  evaluated on expand/copy only (upstream #4607)
- settled threads render 10 rows per project group initially and page by
  25 via a show-more row, resetting on environment/search changes,
  instead of materializing the whole settled set (upstream Thread List
  v2 paging pattern)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports upstream t3code #4727. listRefs ran a fresh set of git
branch/for-each-ref/worktree subprocess batches per call, so N
concurrent consumers of one repo caused N subprocess storms. Now a
ref-snapshot cache keyed by git-common-dir single-flights concurrent
scans, a short TTL keeps warm snapshots, and every mutating git
operation invalidates the snapshot. Repository path lookups coalesce
the same way, and the background upstream-fetch used for status backs
off exponentially (30s to 15min per remote) instead of retrying an
unreachable remote every 5s. The refresh flag stays a server-local
input extension until a client needs it over the wire.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snoozing (ports upstream t3code #4311, backend only): snooze is an
overlay on the active lifecycle, not a fourth destination. thread.snooze
carries an ISO wake time; clients suppress the thread until snoozedUntil
passes (timer wakes emit no event) and the decider emits
thread.unsnoozed(reason: "activity") when a user message arrives.
Covers contracts (commands/events/shell+thread fields, threadSnooze
capability), decider with lifecycle-reset extraction, projector,
projection pipeline/query, migration 040, and client-runtime
commands/reducer/threadSettled additions, with the upstream decider and
snoozed-state test suites.

replayEvents (ports upstream t3code #4791 part 2): the RPC is dead — TS
clients resume via afterSequence catch-up and mac now does the same —
so drop the contract, the ws.ts handler (which collected an unbounded
event tail into memory), the project-event enrichment machinery only it
used, and its tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snoozing: T3Kit gains thread.snooze/thread.unsnooze commands and the
snoozedUntil/snoozedAt shell fields; the sidebar context menu offers a
Snooze submenu (1 hour, 3 hours, tomorrow 9 AM, a week) and Wake for
snoozed threads. Snoozed threads ride the settled disclosure until the
wake time passes — a passed snoozedUntil simply stops classifying as
snoozed on the next evaluation, mirroring the server contract where
timer wakes emit no event.

replayEvents: remove the never-called client wrapper and input model;
reconnect catch-up is the afterSequence resume cursor on each stream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snoozed threads leave the active list (home, navigation groups, scenery
occupancy) via a single isThreadOutOfInbox choke point and rejoin the
settled section with a moon icon and a snoozed-until hint. Long-press
menu offers Wake or Snooze presets (1 hour, this evening, tomorrow 9am,
next week — upstream Sidebar.snooze.ts semantics), dispatched through
the client-runtime snooze/unsnooze commands with the same in-flight
guard and failure alert as settle. A passed wake time reappears without
any event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SergeSerb2
SergeSerb2 merged commit 2c5513b into main Jul 29, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant