Repository navigation
perf(web): pull request chains parse each link's URL once - #21147
Conversation
The chat view and every sidebar row resolve a thread's current pull request on each render, and chain resolution normalized every link's key several times, parsing its URL each time. A thread with 157 linked PRs spent ~1.5 ms per resolve and several hundred ms over a streamed turn. Keys are now cached per link object, which stays stable until the link changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a contained performance optimization that caches normalized pull-request keys without changing the existing chain-resolution behavior for immutable links. It introduces no new capability, schema change, default change, or sensitive-path modification. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 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.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
## What's Changed * perf(web): switching threads no longer re-renders the chat view for an unchanged preview list by @juliusmarminge in pingdotgg/t3code#21130 * fix(server): opening a large database no longer blocks startup for minutes by @t3dotgg in pingdotgg/t3code#21122 * perf(web): switching threads no longer re-renders the outgoing thread's rows by @juliusmarminge in pingdotgg/t3code#21123 * perf(web): reasoning rows skip the tool-failure text scan by @juliusmarminge in pingdotgg/t3code#21146 * perf(web): pull request chains parse each link's URL once by @juliusmarminge in pingdotgg/t3code#21147 **Full Changelog**: pingdotgg/t3code@v0.1.1-nightly.20261018.3302...v0.1.1-nightly.20261018.3315 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.1-nightly.20261018.3315
## What's Changed * perf(web): switching threads no longer re-renders the chat view for an unchanged preview list by @juliusmarminge in pingdotgg/t3code#21130 * fix(server): opening a large database no longer blocks startup for minutes by @t3dotgg in pingdotgg/t3code#21122 * perf(web): switching threads no longer re-renders the outgoing thread's rows by @juliusmarminge in pingdotgg/t3code#21123 * perf(web): reasoning rows skip the tool-failure text scan by @juliusmarminge in pingdotgg/t3code#21146 * perf(web): pull request chains parse each link's URL once by @juliusmarminge in pingdotgg/t3code#21147 **Full Changelog**: pingdotgg/t3code@v0.1.1-nightly.20261018.3302...v0.1.1-nightly.20261018.3315 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.1-nightly.20261018.3315
Brings in upstream through pingdotgg#21147 (9c16146). - Branch picker (pingdotgg#20822, "use memo" removed): reapplied Cody's worktree base selection (refs keyed with refs/heads and refs/remotes, default and picked bases stored as the draft's base ref) on the inlined picker items. - Scheduled task editor (pingdotgg#20807 family, editor split into a form component): reapplied Cody's Post runs to field, thread reset on project or environment change, and hidden workspace fields for thread-bound tasks. - Superseded: none. Adapted: none.
Merges `pingdotgg/t3code` up to `ab6be13d43`, from base `5c5dcbc60b`: 589 commits. Branched from `main` (`7e132898fa`), with no other merge PR open. Most of the range is the rest of upstream's move to the React Compiler, plus its apps/web DOM tests moving to vitest browser mode (`*.browser.test.tsx`, run in real Chromium). Upstream removed jsdom and `react-test-renderer`. ## How the conflicts were resolved All 27 conflicted files were resolved by the verdict `preflight.mjs` printed for each. The tracker entry in `docs/fork/upstream-merge-log.md` has the details. **Tests** - Seven upstream tests became browser tests. The fork's mocks were re-ported into the new files, and the inventory was re-pointed at them. - The fork's framed-preview tests moved to the fork-only `PreviewView.frame.browser.test.tsx`. A browser-mode mock binds its exports once, at load, so one file can't run both with and without the desktop bridge. - Three fork-only tests (ThreadForkDialog, MoatlessArtifactChip, MobileSidebarDrawer) depended on `react-test-renderer` and on test helpers upstream deleted. They are rewritten as browser tests. **ChatView** - Upstream moved the rewind dialog into `chat/ChatOverlayHosts.tsx`, and the `checkpointFileRestore` gate moved with it. - The ThreadDetailsPanel script props were re-applied in upstream's inline JSX. **Lockfile and package.json** - `pnpm-lock.yaml` was re-derived with `install.mjs`. - `apps/web/package.json` keeps the `moatless-api` workspace link and the `tsc --noEmit` typecheck script. **Converged** - Upstream now renders Mermaid itself (`chat/MermaidDiagram.tsx`). The fork's `fork/mermaidDiagram*` files, their inventory entries and the Mermaid Diagram Delta section are deleted. **Fixed along the way** - The provider list's own "Add provider" button had no gate, on `main` too. It is now behind `providerConfiguration`, like the title button. - Upstream's React Compiler lint rules flagged 64 errors in fork-owned files, and the compiler-coverage test found 2 bailouts (`finally` inside a hook). All are fixed. - Most were `useMemo`/`useCallback` removals. - The only behaviour change: sandbox "elapsed" times are now measured from the status query's `dataUpdatedAt` instead of a `Date.now()` read during render. ## Verification `verify.mjs --sequential`: duplicate-adds, tripwires, resolution-check, unsupported-methods (ADD 0 / DROP 0 / KEEP 2), lockfile, fmt, lint, typecheck and build are all green. The `test` step is red only on the five files that failed alone on 2026-10-17 too, on the same machine-only causes: - `shared` `AgentScope` - `ssh` `tunnel` - `provider-opencode` `OpenCodeServerLedger` - `t3` `AcpAdapterV2` - `t3` `OrchestratorReplayFixtures` `@t3tools/web` failed in the full run, and re-ran green after the fixes: 565 files, 8497 tests. The failure that wasn't a compiler bailout, `WorkflowOpeningBlock.browser.test.tsx`, is upstream's file unchanged, and it passed alone. ## Feature classification ### Usable as-is - **Reasoning-effort stepping**: new `composer.increaseEffort` and `composer.decreaseEffort` commands. They have no default binding, and keybinding editing stays behind `serverAdministration`. - **Transcript and markdown fixes**: - Windows paths and terminal links (pingdotgg#21069, pingdotgg#21199, pingdotgg#19627) - Math in Mermaid diagrams (pingdotgg#16198) - Code-block highlight jump (pingdotgg#20923) - Copied task lists (pingdotgg#14778) - Table expand button (pingdotgg#19782) - **Message-list fixes**: - Huge-thread scroll loop (pingdotgg#21269) - Answered questions stay visible (pingdotgg#21086) - "Failed" status for failed runs (pingdotgg#20996) - Resumed-subagent rows (pingdotgg#18099) - Background-agent count (pingdotgg#20785) - A single completion sound (pingdotgg#17540) - **Composer fixes**: - Spacing and centring (pingdotgg#21345, pingdotgg#12952, pingdotgg#17820, pingdotgg#21241) - Quotes (pingdotgg#20350, pingdotgg#19450) - Send shortcut on follow-ups (pingdotgg#20929) - `/tmp/x` no longer opens the slash menu (pingdotgg#19671) - Failed attachments don't block send (pingdotgg#20737) - HEIC attachments (pingdotgg#10429) - Long approval commands wrap (pingdotgg#17802) - Android Backspace (pingdotgg#18063) - **Thread drag and drop into chat** (pingdotgg#19697, pingdotgg#20024). - **Diff, file and pull request panel fixes**: pingdotgg#20297, pingdotgg#20333, pingdotgg#19089, pingdotgg#18288, pingdotgg#17132, pingdotgg#21048, pingdotgg#17554, pingdotgg#19945, pingdotgg#15341, pingdotgg#21245, pingdotgg#21147. - **Sidebar and breadcrumb fixes**: middle truncation (pingdotgg#18858, pingdotgg#17208), "No project" scratch breadcrumb (pingdotgg#19721), and snoozed threads waking in an open chat (pingdotgg#21019). - **Settings and miscellaneous UI fixes**: - Model picker lists every custom model (pingdotgg#14961) - Font picker (pingdotgg#9814) - Settings search highlight (pingdotgg#20764) - Usage popover (pingdotgg#19808, pingdotgg#16936) - Web app manifest name (pingdotgg#19175) - Dark-mode switches (pingdotgg#14984) - **Phone browser**: tapping away from the model picker no longer presses what is under it (3f2dbd7). - **client-runtime**: queries stay pending during a connection hand-off (pingdotgg#18155), and a dropped session is not shown as a failed connection (pingdotgg#18469). - **React Compiler and render-performance work**: about 190 commits. - **Workflow thread view**: `WorkflowOpeningBlock` shows a workflow's phases and agents (pingdotgg#20857). It renders only when `workflowScript` is set, which Moatless never sends; see the backend list. ### Unsupported in Moatless / needs implementation - **`t3 doctor`, server telemetry and the Node CLI** (pingdotgg#20948, pingdotgg#15028, pingdotgg#15921, pingdotgg#15913): upstream's server only, with no client surface. - **SSH device hosts and T3 Connect/relay fixes** (pingdotgg#20377, pingdotgg#17440, pingdotgg#18832, pingdotgg#19444, pingdotgg#18621, pingdotgg#17743, pingdotgg#18838): behind `FEATURES.connections` or `FEATURES.deviceHub`. - **Tailscale HTTPS row** (pingdotgg#21057): behind `FEATURES.connections`. - **Remote server self-update and how-to-update messages** (pingdotgg#18980, pingdotgg#18110): behind `FEATURES.serverUpdateBanner`. - **Masking of secret settings fields** (pingdotgg#20446): the form is behind `FEATURES.providerConfiguration`, because Moatless does not dispatch `server.updateSettings`. - **Desktop updater, desktop preview and local-machine fixes** (pingdotgg#21210, pingdotgg#20994, pingdotgg#16026, pingdotgg#18549, pingdotgg#19846, pingdotgg#20206, pingdotgg#20277): desktop bridge only, or behind `FEATURES.openInEditor`. ### Backend behavior to consider reproducing in Moatless Added to [gaps: runtime fixes](docs/fork/gaps.md#runtime-fixes-upstream-made-to-its-own-server). - **`workflowScript: true` on a conversation message whose body is a provider-native workflow script.** Without it the timeline shows the raw script. - **`endedWatch` on an ended pull request watch**, so watching again doesn't repeat news the thread already got (pingdotgg#21155). - **`pullRequests` on orchestrator MCP thread list and read results** (pingdotgg#17454). Related: `t3_thread_send` now says why a message was refused (pingdotgg#21027). - **`vcs.createWorktree` always sends `path: null`** and expects the server to place the worktree. Moatless doesn't dispatch this method; it is under `projectManagement`. - **Run lifecycle**: - Refuse `thread.create` for an id that already exists (pingdotgg#19704). - Cancel a preparing run on archive (pingdotgg#20073). - A parent Stop clears orphaned delegated follow-ups (pingdotgg#20310). - Queued messages land in order after a resume (pingdotgg#17773). - **Workflow members stream thinking and tool calls into their own threads** (pingdotgg#20786). - **Pull request link handling**: canonicalise legacy links (pingdotgg#19129), don't inherit watches into forks (pingdotgg#16623), and settle threads after their pull request merges (pingdotgg#18232, pingdotgg#19937). No new RPC methods. `vcs.createWorktree` is the only change to `rpc.ts`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Moatless task: https://moatless.soaplabstest.com/tasks/5bcfa708-8eac-41b0-a580-c885a620afb5
The chat view and every sidebar row resolve a thread's current pull request on each render.
resolveThreadPullRequestChainsnormalized each link's key several times per call (stack grouping,branchKeyfor head and base,threadPullRequestKeyOffor placement), and every normalization ranparseChangeRequestUrl, which builds anew URL()and runs a few regexes. A thread with 157 linked PRs (a real stack-heavy thread from the seed data) cost about 1.3–1.6 ms per resolve, several times per ChatView render. Over a streamed turn in that thread this added up to 150–370 ms of URL parsing inside long tasks (CDP traces of three full turns on main).Normalized keys are now cached per link object in a
WeakMap. Links are immutable decoded values, and the shell reducer keeps unchanged links by reference, so the cache hits across renders. A changed link is a new object and gets a fresh key.Measured
Microbenchmark on the real 157-link array (node, 100 iterations after warmup):
resolveThreadCurrentPullRequestLinkresolveThreadPullRequestChainsIn the app: production build (
vp build --minify false), headless Chromium, same seeded snapshot, CDP trace per window, median of 3 runs with the build order alternated:changeRequestUrl.tsms inside long tasks (≥50 ms)threadPullRequests.tsms inside long tasksLong-task counts and total long-task ms for open and switch are within noise (1,363 vs 1,352 ms on switch). This takes a few percent off each frame and doesn't remove any. Streaming, where ChatView re-renders on every delta, is where it adds up (see the numbers above).
Tests:
packages/shared/src/threadPullRequests.test.tspasses unchanged. Lint and typecheck pass forpackages/shared.Claude Opus 5.5 via Claude Code.
🤖 Generated with Claude Code