Skip to content

feat: paginate thread loading with user-anchored turn windows - #5493

Merged
t3dotgg merged 13 commits into
mainfrom
t3code/paginate-thread-loading
Aug 7, 2026
Merged

feat: paginate thread loading with user-anchored turn windows#5493
t3dotgg merged 13 commits into
mainfrom
t3code/paginate-thread-loading

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 6, 2026

Copy link
Copy Markdown
Member

Problem

Long threads are heavy to open, especially on mobile. The heaviest observed thread (161 turns, 23k activities) ships 8.4MB of JSON on every open, all of which must be parsed, held in client state, and persisted to the mobile cache. On many-turn threads, the most recent 10 turns are only 2-6% of the bytes.

Solution

Opt-in pagination of the thread detail snapshot, cut on user-anchored turn boundaries:

  • Server: GET /api/orchestration/threads/:id accepts turnLimit + beforeCursor. The window is everything from the Nth-last turn-with-a-user-pending-message onward, so subagent/fan-out turns ride along and the first page always contains the last N user prompts (verified against real data: fan-out bursts run 35+ consecutive subagent turns). Responses carry page: { beforeCursor, hasMore, snapshotSequence } with an opaque exclusive cursor returning disjoint older slices. A 150-raw-turn ceiling bounds pathological fan-out. The WS fallback snapshot honors the same opt-in via the subscription input.
  • Compatibility: pagination is strictly opt-in and capability-gated (threadSnapshotPagination in server config). Old client + new server and new client + old server both keep full-snapshot behavior.
  • Shared client state: initial loads request the last 10 user turns; loadOlderTurns fetches 20 more per call. Consistency rules: a fresh snapshot replaces all loaded history (no stale-revert resurrection), in-flight pages are discarded when a revert/snapshot/deletion rewrites history or when the page was read from a projection behind the loaded state, and merged pages never advance the live-event dedupe sequence. All covered by state-machine tests.
  • UI: a plain "Load earlier turns" header row on web and mobile; LegendList's maintainVisibleContentPosition anchors scroll on prepend.

No schema migration: the migration-029 indexes cover the bounded queries (verified with EXPLAIN QUERY PLAN against a 45k-activity fixture; worst-case bounded reads run in single-digit ms).

Measured on the heaviest real thread: initial fetch drops from 8.4MB to 1.0MB wire (~1/8th), and a full page-by-page walk reproduces the exact row set of an unwindowed fetch with zero overlap between pages.

Testing

  • 7 new server tests for window resolution, cursor semantics (foreign/malformed cursor degradation, disjointness/coverage walk), and the turnless-thread edge case
  • 7 new client state-machine tests for the race rules
  • Full server (1880) and client-runtime (615) suites green; typecheck and lint clean across the workspace
  • Live smoke against the seeded worktree db: windowed first page returns exactly the last 10 user messages with hasMore, older pages are disjoint, full walk covers all 815 messages

Implemented by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Medium Risk
Touches core thread sync, projection queries, and cache semantics; mistakes could drop history, duplicate streaming text, or serve wrong pages, but behavior is opt-in, capability-gated, and heavily tested.

Overview
Adds opt-in, capability-gated thread detail pagination so long threads open with a small recent window instead of the full history.

Server exposes turnLimit and beforeCursor on HTTP thread snapshots and WS subscribe fallbacks. Windowing walks back user-anchored turns (subagent turns ride along), returns page metadata with an opaque keyset cursor, and uses a new projection_turns keyset index. Malformed or foreign cursors degrade to the first page.

Client-runtime loads the last 10 user turns initially and fetches 20 more per “load earlier” via requestOlderThreadTurns, with epoch/lock/watermark rules so reverts, fresh snapshots, and stale pages cannot corrupt merged history. Thread cache schema bumps to v3 so old clients cannot treat a partial window as complete.

Web and mobile show a Load earlier turns control in the thread feed when hasMore is set.

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

Note

Paginate thread loading with user-anchored turn windows on client and server

  • Adds windowed thread snapshot loading to the server (ProjectionSnapshotQuery, HTTP and WS handlers), returning only the last 10 user-anchored turns initially and supporting cursor-based older-page fetches of 20 turns at a time.
  • Extends EnvironmentThreadState with pagination state (page, loadingOlder, hasMore, cursor) and adds requestOlderThreadTurns / threadHasOlderTurns helpers in the client-runtime state machine.
  • Introduces a semaphore (applyLock) and epoch/watermark guards in the thread state machine to prevent stale or interleaved older-page merges from corrupting live history.
  • Surfaces a "Load earlier turns" header control in both the web (MessagesTimeline) and mobile (ThreadFeed) UIs, wired to requestOlderThreadTurns and reflecting loading state.
  • Adds a new DB migration (037_ProjectionTurnsKeysetIndex.ts) creating a composite keyset index on projection_turns(thread_id, requested_at, turn_id) to support efficient paginated queries.
  • Bumps the thread snapshot cache schema version from 2 to 3; existing v1/v2 cached entries will fail to decode and trigger a full reload.
  • Risk: clients connected to servers that do not advertise threadSnapshotPagination in ServerConfig will have any windowed cache discarded and reload the full thread history.

Macroscope summarized 95cd34a.

@coderabbitai

coderabbitai Bot commented Aug 6, 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: 7ec277cf-6f61-408f-a3c7-14e9c2edeaaa

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

❤️ Share

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

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 6, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Effect service conventions review of the changed TypeScript. One finding: the new UI-to-state-machine channel in packages/client-runtime/src/state/threads.ts routes through mutable module-global state instead of the Effect environment. Server-side additions (threadDetailCursor.ts, windowed ProjectionSnapshotQuery queries, contract/schema additions) follow the import, error, and dependency-acquisition conventions; test harnesses pass service instances explicitly, which is an allowed test seam.

Posted via Macroscope — Effect Service Conventions

Comment thread packages/client-runtime/src/state/threads.ts Outdated
Comment thread packages/client-runtime/src/state/threads.ts Outdated
Comment thread packages/client-runtime/src/state/threads.ts
Comment thread packages/client-runtime/src/state/threads.ts
@macroscopeapp

macroscopeapp Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a substantial new pagination feature for thread loading with complex state management, new API parameters, and multi-platform UI changes. Additionally, there is an open bug report about the loading state getting stuck on disconnect. Human review is appropriate for this scope.

No code changes detected at 95cd34a. Prior analysis still applies.

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

Comment thread packages/client-runtime/src/state/threads.ts Outdated
Comment thread packages/client-runtime/src/state/threads.ts Outdated
Comment thread packages/client-runtime/src/state/threads.ts
Comment thread packages/client-runtime/src/state/threads.ts Outdated
Comment thread packages/client-runtime/src/state/threads.ts Outdated
@t3dotgg
t3dotgg force-pushed the t3code/paginate-thread-loading branch from d4c55c3 to 2c8a2e3 Compare August 6, 2026 21:16
Comment thread packages/client-runtime/src/state/threadSnapshotHttp.ts
Comment thread apps/server/src/orchestration/threadDetailCursor.ts Outdated
Comment thread packages/client-runtime/src/state/threads.ts
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 7, 2026
const pendingOlderPage = yield* Ref.make<{
readonly snapshot: OrchestrationThreadDetailSnapshot;
readonly epoch: number;
} | null>(null);

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.

Parked page sticks loading offline

Medium Severity

The pendingOlderPage state, which holds a parked page and keeps loadingOlder true, isn't cleared when the connection disconnects or encounters a stream error. This leaves the UI stuck on "Loading earlier turns..." and prevents subsequent attempts to fetch older history.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9bc5966. Configure here.

t3dotgg and others added 13 commits August 6, 2026 18:50
…n pages

Adds opt-in pagination to thread detail reads. A windowed request returns
everything from the Nth-last user-anchored turn onward (subagent/fan-out
turns ride along) plus page metadata with an opaque exclusive cursor for
disjoint older slices. Requests without a window keep the full-snapshot
behavior on both HTTP and the WS fallback, so pre-pagination clients are
unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clients gate window requests on threadSnapshotPagination in the server
config, so new clients never send window fields to pre-pagination servers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ge merges

Thread state gains page metadata and a loadOlderTurns flow implementing the
consistency rules: fresh snapshots replace all loaded history, in-flight
older pages are discarded when a revert/snapshot/deletion rewrites history
(epoch check) or when the page was read from a projection behind the loaded
state, and merged pages never advance the live-event dedupe sequence.
Windowed loads are gated on the server's threadSnapshotPagination
capability; servers without it keep full snapshots.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both timelines gain a plain load-more row as the list header, driven by the
shared thread state's page metadata. LegendList's
maintainVisibleContentPosition anchors the scroll position on prepend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ext.Reference

Effect Service Conventions check flagged the module-global handler Map as
hiding the UI-action-to-state-machine dependency. The registry is now a
Context.Reference the machines resolve from the environment (overridable
in tests), with a shared default instance backing the sync
requestOlderThreadTurns entry point so app wiring is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…omic page merges

Addresses three review findings on the pagination PR:

- Stale cursors after revert (high): the server's revert projector rewrites
  projection_turns row ids, invalidating the stored page cursor. On a
  windowed thread, a revert now triggers a fresh windowed snapshot fetch
  (sequence-checked so a lagging projection cannot resurrect reverted
  turns), minting a valid cursor.
- Windowed cache vs pre-pagination server (medium): resuming a windowed
  cache via afterSequence against a server without threadSnapshotPagination
  would render only the window forever. The subscription now drops the
  windowed cache and takes a full snapshot; loadOlderTurns is gated on the
  capability so window params are never sent to old servers.
- Epoch TOCTOU (medium): staleness check and page merge now run under the
  same semaphore as stream-item application, closing the window where a
  revert could land between check and merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…te update

The merge previously read the loaded thread outside SubscriptionRef.update
and committed the result inside it, so a concurrent setThread between read
and commit could be overwritten. The merge now composes with the value the
update callback receives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ility, no-op refresh when fully loaded

Second round of review findings on the revert-refresh path:

- The refresh's staleness check and snapshot application now share one
  applyLock acquisition (via applyItemLocked), so a live event cannot
  advance lastSequence between check and apply and be swallowed by a
  regressing watermark.
- paginationSupported is reset on disconnect: the capability belongs to
  the session that advertised it, and a stale true during reconnect could
  send window params to a newly prepared pre-pagination server.
- The post-revert refresh is skipped when hasMore is false: there is no
  cursor to re-mint and the refresh would discard already-merged older
  pages for nothing. The revert reducer's own filtering handles history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third-party review round on the pagination design:

- Cursors are now an (anchor timestamp, turn id) keyset instead of
  projection_turns.row_id. Row ids are rewritten by the revert projector
  and by projection rebuilds, silently invalidating persisted cursors;
  the keyset is derived from event content and survives both. This
  deletes the client's entire revert-refresh machinery (refresh queue,
  refreshWindowedSnapshot, revert-triggers-refresh wiring) — the revert
  reducer's turn filtering is sufficient on its own. Pinned by a server
  test that rewrites all turn row ids and re-pages with the old cursor.
- Thread cache schema bumped to 3 on web and mobile (rollback safety): a
  pre-pagination client would decode a windowed v2 record, silently drop
  the unknown page field, and treat the partial thread as complete
  forever. v3 records fail its literal match and cold-load instead.
- The window query applies the keyset bound and 150-turn LIMIT in a
  candidates CTE before the window functions run, so a page over a huge
  thread scans a bounded number of turns instead of every older turn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The anchor is COALESCE(requested_at, started_at, '') and the turn key is
COALESCE(turn_id, ''), so a server-minted cursor can legitimately carry
empty strings; the decoder rejected them as malformed, degrading a valid
cursor to a first-page request that repeats recent history. Adds a codec
test file covering round-trips (including empty boundaries) and malformed
input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two blockers from external review:

- Pages now carry threadSequence, the highest thread-detail event sequence
  applied at read time (filtered to the exact event types the subscription
  delivers, so the watermark is always reachable). A page read ahead of the
  client's live state parks until events catch up, closing the race where a
  streaming turn outside the loaded window had its deltas replayed on top
  of page content that already included them, duplicating text. Pages from
  pre-watermark servers merge immediately (old behavior).
- The window query's candidates CTE now orders by raw
  (requested_at, turn_id) — requested_at is NOT NULL by schema — and
  migration 037 adds a (thread_id, requested_at, turn_id) index, so the
  keyset range and order are both index-served with no temp B-tree: the
  scan is genuinely bounded by the page LIMIT. Keyset comparisons keep
  COALESCE only on the turn_id tiebreak, which does not affect index use.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotgg force-pushed the t3code/paginate-thread-loading branch from 9bc5966 to 95cd34a Compare August 7, 2026 01:51

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

There are 2 total unresolved issues (including 1 from previous review).

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 95cd34a. Configure here.

? { minAnchorAt: "", minTurnKey: "", beforeAnchorAt: "", beforeTurnKey: "" }
: undefined;

const thread = yield* getThreadDetailByIdBounded(threadId, emptyBounds ?? bounds);

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.

Pre-turn rows dropped from pages

Medium Severity

Windowed reads only open the lower bound for turnless rows when the thread has no turns at all. If any turns exist, minAnchorAt stays at the oldest returned turn’s requested_at even when hasMore is false, so messages and activities with turn_id null and created_at before that first turn never appear on any page. That breaks the claimed full page-walk parity with an unwindowed fetch for pre-turn content such as early context-window.updated activities.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 95cd34a. Configure here.

@t3dotgg
t3dotgg merged commit 6b73b3d into main Aug 7, 2026
17 checks passed
@t3dotgg
t3dotgg deleted the t3code/paginate-thread-loading branch August 7, 2026 02:33
t3dotgg added a commit that referenced this pull request Aug 7, 2026
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
stevesarmiento added a commit to stevesarmiento/harness that referenced this pull request Aug 7, 2026
…ffect beta.103

67 upstream commits (through 6b73b3d): paginated thread loading with
user-anchored turn windows (pingdotgg#5493), contract-backed fonts (pingdotgg#5103),
subagent/workflow observability agents panel (pingdotgg#5219), sidebar-v2 thread
pinning (pingdotgg#5312, migrations 036/037), scannable pairing QR (pingdotgg#5360), MCP
tool-result trimming (pingdotgg#5482), renderer OOM containment (pingdotgg#5148), tunnel
survives updates/stops, terminal polish batch, forward-compat
ServerProviders decode (pingdotgg#5327), Effect beta.103, many fixes.

Forma preserved: composer surface/footer, ComposerMetaBar, diff panel
skin, RightPanelTabs Forma strip/icons with BOTH componentPreview and
agents surfaces, sidebar tone map, forma:// CORS origins, ~/.forma,
fork migrations 935-941 after upstream 036/037, fork contract fields
alongside fonts/threadPinning. Fonts unification starts (terminal now
settings-driven); fix-forward follows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Eight commits, headlined by pingdotgg#5493 "paginate thread loading with user-anchored
turn windows" -- 2093 insertions plus a keyset index migration.

Candidate #29 ("perf: import bounded web thread history", upstream pingdotgg#4018) is
confirmed superseded: upstream never merged pingdotgg#4018 and ships loadOlderTurns
instead. The candidate is removed from web, mobile and client-runtime --
olderThreadActivities.ts deleted, ChatView and the mobile composer/feed/screens
rewired to upstream's loadEarlier model. The SERVER half stays: deployed mobile
builds still call orchestration.getThreadActivities, so the RPC, its schemas and
the activity window constant are retained as a compatibility surface and marked
as such.

All 24 textual conflicts resolved to upstream. The expensive work was what git
auto-merged wrong or left dangling, found by typecheck and tests, not markers:

- threads.ts: upstream's applyItemLocked header welded onto the fork's batch
  reducer body, referencing an out-of-scope identifier and silently dropping
  upstream's synchronized branch. The fork's batching layer (groupedWithin,
  reduceThreadStreamItems, eventBatchSize) is removed with its two tests; the
  fork's load-once HTTP fallback guard is reimplemented on upstream's model and
  its regression test passes again.
- ws.ts: the fork's reuseBaseBranch worktree flow and upstream's pingdotgg#5556
  no-origin fallback are combined; neither side alone compiled.
- ProjectionSnapshotQuery: upstream's new windowed message query lacked the
  fork's source_json column, failing decode on every windowed read; the bounded
  detail query destructured nine results from seven queries -- the fork's
  queued-messages and pending-turn-start members are restored.
- BranchToolbar/SidebarV2/MessagesTimeline/ChatView: prop and rename skews
  reconciled; fork surface-existence assertion updated for the new feed call.
- Migration ledger fixtures extended for upstream migration 037, which lands in
  the upstream namespace and does not collide with the fork's renumbered 037.
- Upstream's new tests adapted to fork-required fields (queuedMessages,
  pendingTurnStart) and the fork's projection-wait in bootstrap.

Verified: full recursive typecheck clean across 17 packages; 2264 tests pass
including upstream's 11 pagination and 7 windowed-detail tests. The single
failure (CodexTextGeneration structured output) predates this merge.

fork/tim touches threads.ts, ProjectionSnapshotQuery and contracts; no tim
commit is merged upstream, so review those diffs with tim provenance in mind.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Eight commits, headlined by pingdotgg#5493 "paginate thread loading with user-anchored
turn windows" -- 2093 insertions plus a keyset index migration.

Candidate #29 ("perf: import bounded web thread history", upstream pingdotgg#4018) is
confirmed superseded: upstream never merged pingdotgg#4018 and ships loadOlderTurns
instead. The candidate is removed from web, mobile and client-runtime --
olderThreadActivities.ts deleted, ChatView and the mobile composer/feed/screens
rewired to upstream's loadEarlier model. The SERVER half stays: deployed mobile
builds still call orchestration.getThreadActivities, so the RPC, its schemas and
the activity window constant are retained as a compatibility surface and marked
as such.

All 24 textual conflicts resolved to upstream. The expensive work was what git
auto-merged wrong or left dangling, found by typecheck and tests, not markers:

- threads.ts: upstream's applyItemLocked header welded onto the fork's batch
  reducer body, referencing an out-of-scope identifier and silently dropping
  upstream's synchronized branch. The fork's batching layer (groupedWithin,
  reduceThreadStreamItems, eventBatchSize) is removed with its two tests; the
  fork's load-once HTTP fallback guard is reimplemented on upstream's model and
  its regression test passes again.
- ws.ts: the fork's reuseBaseBranch worktree flow and upstream's pingdotgg#5556
  no-origin fallback are combined; neither side alone compiled.
- ProjectionSnapshotQuery: upstream's new windowed message query lacked the
  fork's source_json column, failing decode on every windowed read; the bounded
  detail query destructured nine results from seven queries -- the fork's
  queued-messages and pending-turn-start members are restored.
- BranchToolbar/SidebarV2/MessagesTimeline/ChatView: prop and rename skews
  reconciled; fork surface-existence assertion updated for the new feed call.
- Migration ledger fixtures extended for upstream migration 037, which lands in
  the upstream namespace and does not collide with the fork's renumbered 037.
- Upstream's new tests adapted to fork-required fields (queuedMessages,
  pendingTurnStart) and the fork's projection-wait in bootstrap.

Verified: full recursive typecheck clean across 17 packages; 2264 tests pass
including upstream's 11 pagination and 7 windowed-detail tests. The single
failure (CodexTextGeneration structured output) predates this merge.

fork/tim touches threads.ts, ProjectionSnapshotQuery and contracts; no tim
commit is merged upstream, so review those diffs with tim provenance in mind.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Eight commits, headlined by pingdotgg#5493 "paginate thread loading with user-anchored
turn windows" -- 2093 insertions plus a keyset index migration.

Candidate #29 ("perf: import bounded web thread history", upstream pingdotgg#4018) is
confirmed superseded: upstream never merged pingdotgg#4018 and ships loadOlderTurns
instead. The candidate is removed from web, mobile and client-runtime --
olderThreadActivities.ts deleted, ChatView and the mobile composer/feed/screens
rewired to upstream's loadEarlier model. The SERVER half stays: deployed mobile
builds still call orchestration.getThreadActivities, so the RPC, its schemas and
the activity window constant are retained as a compatibility surface and marked
as such.

All 24 textual conflicts resolved to upstream. The expensive work was what git
auto-merged wrong or left dangling, found by typecheck and tests, not markers:

- threads.ts: upstream's applyItemLocked header welded onto the fork's batch
  reducer body, referencing an out-of-scope identifier and silently dropping
  upstream's synchronized branch. The fork's batching layer (groupedWithin,
  reduceThreadStreamItems, eventBatchSize) is removed with its two tests; the
  fork's load-once HTTP fallback guard is reimplemented on upstream's model and
  its regression test passes again.
- ws.ts: the fork's reuseBaseBranch worktree flow and upstream's pingdotgg#5556
  no-origin fallback are combined; neither side alone compiled.
- ProjectionSnapshotQuery: upstream's new windowed message query lacked the
  fork's source_json column, failing decode on every windowed read; the bounded
  detail query destructured nine results from seven queries -- the fork's
  queued-messages and pending-turn-start members are restored.
- BranchToolbar/SidebarV2/MessagesTimeline/ChatView: prop and rename skews
  reconciled; fork surface-existence assertion updated for the new feed call.
- Migration ledger fixtures extended for upstream migration 037, which lands in
  the upstream namespace and does not collide with the fork's renumbered 037.
- Upstream's new tests adapted to fork-required fields (queuedMessages,
  pendingTurnStart) and the fork's projection-wait in bootstrap.

Verified: full recursive typecheck clean across 17 packages; 2264 tests pass
including upstream's 11 pagination and 7 windowed-detail tests. The single
failure (CodexTextGeneration structured output) predates this merge.

fork/tim touches threads.ts, ProjectionSnapshotQuery and contracts; no tim
commit is merged upstream, so review those diffs with tim provenance in mind.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Eight commits, headlined by pingdotgg#5493 "paginate thread loading with user-anchored
turn windows" -- 2093 insertions plus a keyset index migration.

Candidate #29 ("perf: import bounded web thread history", upstream pingdotgg#4018) is
confirmed superseded: upstream never merged pingdotgg#4018 and ships loadOlderTurns
instead. The candidate is removed from web, mobile and client-runtime --
olderThreadActivities.ts deleted, ChatView and the mobile composer/feed/screens
rewired to upstream's loadEarlier model. The SERVER half stays: deployed mobile
builds still call orchestration.getThreadActivities, so the RPC, its schemas and
the activity window constant are retained as a compatibility surface and marked
as such.

All 24 textual conflicts resolved to upstream. The expensive work was what git
auto-merged wrong or left dangling, found by typecheck and tests, not markers:

- threads.ts: upstream's applyItemLocked header welded onto the fork's batch
  reducer body, referencing an out-of-scope identifier and silently dropping
  upstream's synchronized branch. The fork's batching layer (groupedWithin,
  reduceThreadStreamItems, eventBatchSize) is removed with its two tests; the
  fork's load-once HTTP fallback guard is reimplemented on upstream's model and
  its regression test passes again.
- ws.ts: the fork's reuseBaseBranch worktree flow and upstream's pingdotgg#5556
  no-origin fallback are combined; neither side alone compiled.
- ProjectionSnapshotQuery: upstream's new windowed message query lacked the
  fork's source_json column, failing decode on every windowed read; the bounded
  detail query destructured nine results from seven queries -- the fork's
  queued-messages and pending-turn-start members are restored.
- BranchToolbar/SidebarV2/MessagesTimeline/ChatView: prop and rename skews
  reconciled; fork surface-existence assertion updated for the new feed call.
- Migration ledger fixtures extended for upstream migration 037, which lands in
  the upstream namespace and does not collide with the fork's renumbered 037.
- Upstream's new tests adapted to fork-required fields (queuedMessages,
  pendingTurnStart) and the fork's projection-wait in bootstrap.

Verified: full recursive typecheck clean across 17 packages; 2264 tests pass
including upstream's 11 pagination and 7 windowed-detail tests. The single
failure (CodexTextGeneration structured output) predates this merge.

fork/tim touches threads.ts, ProjectionSnapshotQuery and contracts; no tim
commit is merged upstream, so review those diffs with tim provenance in mind.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
patroza pushed a commit to patroza/t3code that referenced this pull request Aug 7, 2026
Bring upstream pingdotgg#5493 thread turn windows, loadEarlier/keyset reads, and
client-runtime thread sync onto current fork/dev as a single linear commit
(rebase-mergeable). Drop superseded getThreadActivities/olderThreadActivities;
keep fork queue/identity/pendingTurnStart and surface markers. Includes the
green CI tip fixes (migration 37 ledger, source_json projection, remoteExists
bootstrap mock, mobile surface tests).
juliusmarminge added a commit that referenced this pull request Aug 7, 2026
…eysetIndex

Main owns migration numbering: 037_ProjectionTurnsKeysetIndex landed on
main (#5493), so the v2 migrations shift from 037-045 to 038-046.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Aug 7, 2026
Native subagent observability (#5219), wired per its spec's v2 merge plan:
- getWorkflowScript RPC re-homed onto the v2 WS surface (contracts, rpc
  group, ws handler, auth scope, client atom).
- AgentsPanel fed by the spec's mapper swap: projectedSubagentsToRuntime
  maps orchestration-v2 subagent entities into the panel model;
  deriveAgentPanelModel's v2Projection leg is now live and the v1 fold
  never runs. Agents surface wired into ChatView + RightPanelTabs.
Other ports and reconciliations:
- Shell reconnect-loop fix (#5561) ported into the v2 shell sync
  (same-session resubscribes resume from the in-memory cursor), with the
  cursor-resume regression test adapted to v2 fixtures.
- Mobile end-follow latch (#5566) ported onto the v2 ThreadFeed.
- Claude ede_diagnostic interrupt classification (#5557) ported into
  ClaudeAdapterV2 (aborted_tools/aborted_streaming => interrupted; CLI
  telemetry never becomes the failure banner). #5559 needs no v2 port
  (unknown system subtypes are already ignored).
- Plan sidebar removed from the v2 ChatView/ChatComposer per main's
  plans-fold-into-chat rework (#5558); rightPanelStore stays at main's
  surface set.
- SettingsPanels rebuilt as main's refactored version plus the branch's
  composer-context setting; sidebar snooze respects the time format
  (#4438 follow-through).
- v1-only leftovers deleted: zombie v1 adapters/ingestion/tests the v2
  rewrite removes, the v1-bound transfer-budget CI harness (#5350, needs
  a v2 rebuild), and main's v1 client pagination machinery (#5493 client
  side; the 037 keyset migration is kept — server-side v2 windowing is a
  follow-up).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Aug 7, 2026
## What's Changed
* fix(mobile): reduce thread feed scroll jank by @gabrielelpidio in pingdotgg/t3code#4874
* fix(web): restore sidebar v2 thread actions and terminal icon by @Noojuno in pingdotgg/t3code#4712
* fix(web): settle button now works on hover, not just right-click by @t3dotgg in pingdotgg/t3code#4905
* fix(clients): disable add project while disconnected by @StiensWout in pingdotgg/t3code#4834
* fix(composer): hide default Codex service tier by @maxktz in pingdotgg/t3code#4784
* docs: link iOS and Android app store downloads by @t3dotgg in pingdotgg/t3code#4902
* fix(web): align remote server update action by @StiensWout in pingdotgg/t3code#4731
* 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
* fix(mobile): support dragged images in the composer by @t3dotgg in pingdotgg/t3code#4953
* fix(mobile): stop long iOS threads from jumping while scrolling up by @t3dotgg in pingdotgg/t3code#4867
* fix(web): keep worktree default when switching a draft's machine by @t3dotgg in pingdotgg/t3code#4964
* perf(mobile): reconnect environments immediately on resume by @t3dotgg in pingdotgg/t3code#4878
* feat(web): pasting a huge screenshot now compresses it instead of erroring by @t3dotgg in pingdotgg/t3code#4967
* feat(web): regenerate thread titles from sidebar by @t3dotgg in pingdotgg/t3code#4810
* fix(web): show server update progress through reconnect by @t3dotgg in pingdotgg/t3code#4903
* feat(search): find threads by conversation content by @t3dotgg in pingdotgg/t3code#4959
* fix: marketing site Vercel builds no longer die after ~100 deploys by @t3dotgg in pingdotgg/t3code#4975
* docs: split user and maintainer docs, fix 100+ stale claims by @t3dotgg in pingdotgg/t3code#4807
* fix(web): server updates no longer look like warnings by @t3dotgg in pingdotgg/t3code#4992
* fix(connect): reboots no longer strand the relay link, 403s now say why by @t3dotgg in pingdotgg/t3code#4988
* Add project file picker (⌘P) and project content search (⇧⌘F) by @jakeleventhal in pingdotgg/t3code#4855
* Check for mobile app updates on launch by @juliusmarminge in pingdotgg/t3code#4958
* fix(mobile): support pre-Liquid-Glass iOS bottom toolbar by @gabrielelpidio in pingdotgg/t3code#4984
* fix(server): restore PR detection without HOME by @StiensWout in pingdotgg/t3code#4985
* fix(web): fill fast mode icon by @maria-rcks in pingdotgg/t3code#5004
* fix: cache project favicons across web and mobile by @gabrielelpidio in pingdotgg/t3code#4767
* perf(ci): cut stale runs and redundant setup by @t3dotgg in pingdotgg/t3code#4802
* style(web): make scroll-to-end pill translucent by @maria-rcks in pingdotgg/t3code#5036
* fix(desktop): bump Clerk Electron SDK to 0.0.24 and register t3code:// scheme on Linux by @juliusmarminge in pingdotgg/t3code#5015
* perf(server): cache default branch name and origin existence across status refreshes by @UtkarshUsername in pingdotgg/t3code#5008
* feat(shared): support shorthand (major-only) versions in the semver helpers by @arhxam in pingdotgg/t3code#5027
* fix(mobile): remove unnecessary photo library permission by @skjiisa in pingdotgg/t3code#4929
* fix(shared): lenient JSON parser deletes commas inside string values by @arhxam in pingdotgg/t3code#5025
* fix(mobile): default bare IP pairing to HTTP by @Lucenx9 in pingdotgg/t3code#4990
* fix(mobile): restore iOS Threads branding by @PixPMusic in pingdotgg/t3code#4862
* chore: add mobile issue template area by @MaxAnderson95 in pingdotgg/t3code#4895
* fix(desktop): link release notes from the update downloaded toast by @Sy-D in pingdotgg/t3code#4771
* fix(mobile): accept Android clipboard images in composer by @adityavardhansharma in pingdotgg/t3code#4836
* fix(mobile): show the correct build channel in Android's Threads page header by @PixPMusic in pingdotgg/t3code#4861
* fix(contracts): decode growing config unions forward-compatibly by @juliusmarminge in pingdotgg/t3code#5055
* fix(mobile): server model, worktree, and origin preferences now apply to new tasks by @t3dotgg in pingdotgg/t3code#5064
* fix(ci): repair the mobile showcase screenshots workflow by @juliusmarminge in pingdotgg/t3code#5057
* fix(ci): capture iPad App Store screenshots in landscape by @PixPMusic in pingdotgg/t3code#5065
* fix(oxlint-plugin): Resolve the oxlint bin without assuming a pnpm layout by @mwolson in pingdotgg/t3code#5066
* feat(cli): `npx t3 pair` - generate QR code from a running server by @t3dotgg in pingdotgg/t3code#4955
* fix(server): self-update no longer rolls itself back on restart by @t3dotgg in pingdotgg/t3code#5095
* fix(ci): rotate iPad showcase captures without Simulator UI scripting by @juliusmarminge in pingdotgg/t3code#5094
* feat(web): render terminals with libghostty-vt by @StiensWout in pingdotgg/t3code#4860
* refactor: move the canonical libghostty-vt vendor to the repository root by @StiensWout in pingdotgg/t3code#5102
* feat(mobile): add thread snoozing by @gabrielelpidio in pingdotgg/t3code#5053
* feat(mobile): make settled threads collapsible by @PixPMusic in pingdotgg/t3code#5056
* follow-up normalization after #4700. by @shivamhwp in pingdotgg/t3code#4498
* feat(web): search threads from the sidebar by @shivamhwp in pingdotgg/t3code#4769
* feat(web): add settings sidebar search by @shivamhwp in pingdotgg/t3code#4682
* fix: threads with open PRs no longer auto-settle by @t3dotgg in pingdotgg/t3code#5151
* fix(server): bound thread catch-up replay and stop full-DB snapshot hydration by @t3dotgg in pingdotgg/t3code#5147
* fix(server): follow branch drift in dedicated worktrees so PRs link to their thread by @t3dotgg in pingdotgg/t3code#5159
* fix(server): make remote updates rollback-safe by @t3dotgg in pingdotgg/t3code#5181
* fix(web): stop settle controls overlapping the status label by @ipanasenko in pingdotgg/t3code#4574
* fix: normalize app icon glyph sizing by @t3-code[bot] in pingdotgg/t3code#5202
* fix(server): surface cloudflared FTL/PNC relay logs as warnings, not debug by @arhxam in pingdotgg/t3code#5076
* fix(server): stop npx service updates from silently leaving the old server running by @t3dotgg in pingdotgg/t3code#5217
* feat: fold legacy models into separate menus by @t3dotgg in pingdotgg/t3code#5190
* fix(desktop): claim the t3code:// scheme default on Linux at startup by @juliusmarminge in pingdotgg/t3code#5054
* fix(web): polish interface spacing by @maria-rcks in pingdotgg/t3code#5252
* fix(desktop): Niri/Hyprland - Linux secret storage backend by @mwolson in pingdotgg/t3code#2916
* fix(web): match loading screen to dark theme by @t3-code[bot] in pingdotgg/t3code#5303
* fix(web): blink the terminal cursor again by @StiensWout in pingdotgg/t3code#5314
* fix(terminal): protect held Ctrl/Cmd+W close shortcut by @StiensWout in pingdotgg/t3code#5322
* fix(server): strip replayable terminal queries from history by @StiensWout in pingdotgg/t3code#5319
* fix(contracts): decode ServerProviders forward-compatibly by @Brechard in pingdotgg/t3code#5327
* fix(web): simplify chat code blocks by @t3-code[bot] in pingdotgg/t3code#5301
* fix(web): align multiline error alert controls by @t3-code[bot] in pingdotgg/t3code#5304
* Upgrade Effect to beta.103 by @juliusmarminge in pingdotgg/t3code#5331
* fix(web): increase tooltip z-index to overlay popovers and menus by @naMqe-h in pingdotgg/t3code#5326
* fix(server): use a Cursor todo's title when its content is blank by @arhxam in pingdotgg/t3code#5073
* fix(ssh): isolate managed tunnel processes by @nateEc in pingdotgg/t3code#4347
* fix(server): scrub AppImage XDG_DATA_DIRS and GSETTINGS_SCHEMA_DIR from terminals by @arhxam in pingdotgg/t3code#5075
* fix(mobile): show correct provider icons for Grok, Cursor, and OpenCode by @Wraient in pingdotgg/t3code#4586
* fix(web): stop the chat timeline reading through the provider status banner by @Bil0000 in pingdotgg/t3code#5353
* fix(desktop,web): contain renderer memory growth and recover from renderer OOM crashes by @t3dotgg in pingdotgg/t3code#5148
* fix(server): generate durable thread titles by @t3dotgg in pingdotgg/t3code#5357
* fix(web): better right panel (new diffs styling) by @maria-rcks in pingdotgg/t3code#5260
* fix(server): keep regenerated titles on topic by @t3dotgg in pingdotgg/t3code#5365
* refactor(server): make title prompts plaintext by @t3dotgg in pingdotgg/t3code#5368
* feat(web): configurable fonts and sizes under Settings → Appearance by @StiensWout in pingdotgg/t3code#5103
* feat(sidebar-v2): bring back thread pinning by @t3dotgg in pingdotgg/t3code#5312
* feat(web): make pairing QR codes actually scannable, with endpoint choice by @t3dotgg in pingdotgg/t3code#5360
* fix(desktop,web): improve in-app browser shortcuts and URL behavior by @t3dotgg in pingdotgg/t3code#4703
* fix(web): prevent legacy model picker layout shift by @FllipEis in pingdotgg/t3code#5349
* fix(server): allow remote updates with database migrations by @t3dotgg in pingdotgg/t3code#5374
* fix(mcp): unblock Kimi models in OpenCode with preview tools by @hwanseoc in pingdotgg/t3code#5128
* fix(web): clear main branch lint warnings by @t3dotgg in pingdotgg/t3code#5384
* fix(mobile): preserve grouped project workspaces by @shivamhwp in pingdotgg/t3code#4642
* fix(mobile): prevent Android thread search crash by @shivamhwp in pingdotgg/t3code#5386
* fix(web): truncate long project switcher names by @FllipEis in pingdotgg/t3code#5348
* fix(mobile): avoid double dividers between thread sections by @shivamhwp in pingdotgg/t3code#5391
* fix(web): keep the composer command menu anchored to the composer by @StiensWout in pingdotgg/t3code#5336
* fix(web): restore terminal link hover styles by @StiensWout in pingdotgg/t3code#5382
* fix(ci): isolate releases from shared API rate limits by @t3dotgg in pingdotgg/t3code#5394
* fix(web): keep model picker shortcuts in sync by @t3dotgg in pingdotgg/t3code#5400
* fix(web): keep terminal font settings reliable by @StiensWout in pingdotgg/t3code#5397
* Enrich terminal font previews by @juliusmarminge in pingdotgg/t3code#5428
* fix(web): preserve terminal font size when splitting by @t3-code[bot] in pingdotgg/t3code#5444
* Prevent terminal loading flash by @juliusmarminge in pingdotgg/t3code#5432
* fix: reconnect faster after remote server updates by @t3dotgg in pingdotgg/t3code#5404
* feat: native subagent & workflow observability by @t3dotgg in pingdotgg/t3code#5219
* perf(server): stop shipping full MCP tool results in thread payloads by @t3dotgg in pingdotgg/t3code#5482
* fix(web): closed plan sidebar stays closed when returning to a thread by @t3dotgg in pingdotgg/t3code#5484
* fix: respect time format for sidebar snooze by @huxcrux in pingdotgg/t3code#4438
* fix: smooth remote server updates by @t3dotgg in pingdotgg/t3code#5470
* fix(server): drop superseded tool updates from snapshots by @t3dotgg in pingdotgg/t3code#5483
* fix(web): clarify auto permission fallback by @t3-code[bot] in pingdotgg/t3code#5431
* fix(acp): keep unknown approvals actionable by @t3-code[bot] in pingdotgg/t3code#5430
* fix(mobile): stop thread messages reading through pending cards by @carlosricojr in pingdotgg/t3code#5450
* fix(web): align composer inline chips with prompt text by @StiensWout in pingdotgg/t3code#5495
* fix(web): clear woke state on explicit thread actions by @StiensWout in pingdotgg/t3code#5486
* fix(server): skip origin fetch when creating worktrees in repos without an origin remote by @t3dotgg in pingdotgg/t3code#5556
* fix(web): update tooltip no longer dismisses when scrolling release notes by @t3dotgg in pingdotgg/t3code#5547
* fix(server): stop showing commit/push/PR notices as errors in the work log by @t3dotgg in pingdotgg/t3code#5559
* fix(web): show remote environment for non-Git projects by @t3dotgg in pingdotgg/t3code#5555
* fix(server): stopping a Claude thread no longer shows an ede_diagnostic error by @t3dotgg in pingdotgg/t3code#5557
* fix(web): show one toast when snoozing threads in bulk by @t3dotgg in pingdotgg/t3code#5560
* fix(server): let stopped threads settle immediately by @t3dotgg in pingdotgg/t3code#5553
* feat: paginate thread loading with user-anchored turn windows by @t3dotgg in pingdotgg/t3code#5493
* fix: prevent reconnect loops during server stalls by @gfsaaser24 in pingdotgg/t3code#5561
* fix(server): settle stopped Claude subagents by @t3dotgg in pingdotgg/t3code#5568
* fix: scrolling up during a running thread no longer snaps back to the bottom by @t3dotgg in pingdotgg/t3code#5566
* fix(server): one disconnecting client no longer blocks every reconnect by @t3dotgg in pingdotgg/t3code#5572
* test(server): catch client transfer regressions in CI by @t3dotgg in pingdotgg/t3code#5350
* fix(web): stop the "requests are slow" warning from firing on every provider update by @t3dotgg in pingdotgg/t3code#5570
* fix(web): keep agent panel rows stable by @t3dotgg in pingdotgg/t3code#5569
* docs: ship production T3 Connect public config in .env.example by @t3dotgg in pingdotgg/t3code#5573
* fix(web): plans stop hijacking the UI, fold into chat instead by @t3dotgg in pingdotgg/t3code#5558
* feat(web): remove Build/Plan toggle from the composer by @t3dotgg in pingdotgg/t3code#5551
* feat(web): per-device provider settings by @t3dotgg in pingdotgg/t3code#4479
* fix(mobile): invisible T3 Connect devices can now be seen and removed by @t3dotgg in pingdotgg/t3code#5563
* fix: add missing space before 'GitHub releases page' link on download page by @Mateleo in pingdotgg/t3code#4511
* fix(web): stop the sidebar "Working" label from pulsing by @t3dotgg in pingdotgg/t3code#5580
* feat(web): add modular theme library by @StiensWout in pingdotgg/t3code#5226
* fix(web): reading a thread clears Done; Woke is dismissible by @t3dotgg in pingdotgg/t3code#5579
* perf(dev): faster cold-start for dev web serving by @t3dotgg in pingdotgg/t3code#5584
* fix(dev): agents get --share right on the first try by @t3dotgg in pingdotgg/t3code#5586
* fix(mobile): improve keyboard avoiding by @jmeistrich in pingdotgg/t3code#5451
* fix(web): live background-work banner no longer hides behind the update notice by @t3dotgg in pingdotgg/t3code#5595
* fix(web): stabilize chat timeline positioning by @jmeistrich in pingdotgg/t3code#5449
* feat(server): record runtime mode per turn and on mode changes by @t3dotgg in pingdotgg/t3code#5593
* feat(web): thread actions from the chat header title by @t3dotgg in pingdotgg/t3code#5592
* fix(mobile): avoid iOS terminal reset on clear by @justynleung in pingdotgg/t3code#5440
* fix(mobile): pad scroll views above Android nav bar by @StoneCotton in pingdotgg/t3code#5415
* fix(mobile): keep Android chat text from showing through the composer by @PollyGlot in pingdotgg/t3code#5582
* fix(mobile): repair Clerk auth navigation headers by @gabrielelpidio in pingdotgg/t3code#5140
* Bump mobile app version to 1.0.2 by @juliusmarminge in pingdotgg/t3code#5588
* feat(web): click the pin icon to unpin a thread by @UtkarshUsername in pingdotgg/t3code#5578
* fix(web): show the correctly matching shortcut in new thread button's tooltip by @UtkarshUsername in pingdotgg/t3code#5594

## New Contributors
* @gabrielelpidio made their first contribution in pingdotgg/t3code#4874
* @arhxam made their first contribution in pingdotgg/t3code#5027
* @skjiisa made their first contribution in pingdotgg/t3code#4929
* @Lucenx9 made their first contribution in pingdotgg/t3code#4990
* @MaxAnderson95 made their first contribution in pingdotgg/t3code#4895
* @t3-code[bot] made their first contribution in pingdotgg/t3code#5202
* @Brechard made their first contribution in pingdotgg/t3code#5327
* @naMqe-h made their first contribution in pingdotgg/t3code#5326
* @Bil0000 made their first contribution in pingdotgg/t3code#5353
* @gfsaaser24 made their first contribution in pingdotgg/t3code#5561
* @Mateleo made their first contribution in pingdotgg/t3code#4511
* @jmeistrich made their first contribution in pingdotgg/t3code#5451
* @justynleung made their first contribution in pingdotgg/t3code#5440
* @StoneCotton made their first contribution in pingdotgg/t3code#5415
* @PollyGlot made their first contribution in pingdotgg/t3code#5582

**Full Changelog**: pingdotgg/t3code@v0.0.31...v0.0.32

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant