Skip to content

perf(client): reduce repeated sorting and date formatting - #11019

Merged
shivamhwp merged 6 commits into
pingdotgg:mainfrom
Bil0000:t3code/small-performance-wins
Sep 12, 2026
Merged

perf(client): reduce repeated sorting and date formatting#11019
shivamhwp merged 6 commits into
pingdotgg:mainfrom
Bil0000:t3code/small-performance-wins

Conversation

@Bil0000

@Bil0000 Bil0000 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Thread ordering reparses the same dates during each sort comparison, project selection sorts every matching thread to return one, and hourly usage labels repeatedly construct identical Intl.DateTimeFormat instances. This PR removes that repeated work in three production files.

  • Parse sort timestamps once per thread. Keep the existing order keys, ID/environment tie breaks, stable ordering, and original thread references. Find the latest project thread in one scan.
  • Filter plan updates before sorting them, so unrelated tool activity does not enter the plan sort.
  • Reuse date formatters for explicit time zones, with a cache capped at 16 entries. Leave implicit local-time formatting uncached so system time-zone changes still take effect.

No dependencies, settings, wire contracts, or visual changes.

Benchmarks

Measured actual before/after exports from base 8d8189e67d and this branch. These are function-level wall times, not whole-app CPU, memory, GPU, or battery measurements.

Workload Chromium before Chromium after Less time
Sort 1,000 threads by recent activity 1.401 ms 0.262 ms 81.3%
Sort 1,000 active threads without order keys 1.205 ms 0.306 ms 74.6%
Sort 1,000 pinned threads without order keys 1.317 ms 0.357 ms 72.9%
Select latest project thread from 1,000 threads 1.461 ms 0.208 ms 85.8%
Derive a plan from 500 activities / 5 plan updates 0.01537 ms 0.00495 ms 67.8%
Format 24 hourly usage labels and tooltips 12.115 ms 0.375 ms 96.9%

The usage batch saves about 11.7 ms of main-thread work in this fixture. The plan change is a small absolute saving. Benefits depend on history size and how often the caller runs.

Method: 200 warm-up calls per version, nine batches alternating before/after order, median time per operation. Each batch ran 100 list operations, 10,000 plan operations, or 20 usage batches. Fixtures match the committed benchmark: a fixed timestamp permutation for the threads, sequence-ordered activities, and America/New_York hourly formatting. Timings include allocation and garbage collection and varied between runs. The warm cache is intentional for repeated chart labels.

Separate instrumentation, outside timed runs, counted 5,794 → 1,000 Date.parse calls per list operation and 120 → 0 formatter constructions per warm hourly batch. The formatter cache retains at most 16 instances; total process memory was not measured.

Reproduce with the committed Node benchmark

apps/web/src/performance.bench.ts runs the same six workloads with the existing test runner. Copy that file to a checkout of base 8d8189e67d for the before run. From apps/web in each checkout:

# Base checkout, with the benchmark file copied in
vp test bench src/performance.bench.ts --run --outputJson /tmp/t3-perf-before.json

# PR checkout
vp test bench src/performance.bench.ts --run --compare /tmp/t3-perf-before.json --outputJson /tmp/t3-perf-after.json

Node median results, in the same row order as the browser table:

Workload Node before Node after
Recent threads 1.635046 ms 0.254633 ms
Active threads 1.403014 ms 0.363550 ms
Keyless pinned threads 4.716217 ms 0.391587 ms
Latest project thread 1.488331 ms 0.204262 ms
Plan state 0.014992 ms 0.009198 ms
Hourly labels and tooltips 10.591893 ms 0.587797 ms

Node and browser numbers are separate runs, not interchangeable. The browser measurements use the actual bundled modules in Chromium; the command above reproduces the Node suite.

Verification

  • 407 focused tests passed across shared formatting, shared/web thread ordering, plan state, sidebar, command palette, and timeline logic.
  • Added coverage for invalid dates, both sort modes, ties, archived/project filtering, unchanged input/references, mixed plan activity, explicit time zones, and changes to the system time zone. Existing DST tests pass.
  • 14,400 additional deterministic comparisons against the base exports returned identical outputs and thread references, including mixed order keys, bad dates, plan clears, DST transitions, and cache eviction.
  • Scoped type checks for client-runtime, shared, and web passed. Targeted lint/format checks, web file-discovery check, and production web build passed.
  • Verified with 12 synthetic threads and 500 activities: pinned/recent order, thread navigation, command palette, expanded work log, composer edit/clear, usage chart hover, daily/hourly switching, and wide/narrow layouts.

Shared logic serves web/desktop and mobile callers. Native desktop/mobile runtime tests and power measurements were not performed. No provider adapter or connection behavior changes.

Checklist

  • Small changes limited to repeated client-side work
  • Explained the changes and measured their effect
  • Added regression coverage and a repeatable benchmark
  • No visual or animation change requiring before/after media

Model: GPT-6. Harness: Codex.

Summary by CodeRabbit

  • Improvements

    • Improved date and time formatting performance across usage and activity views.
    • Increased consistency when displaying hourly usage across time zones, including system-default time zone handling.
    • Improved thread ordering and selection reliability, including consistent tie-breaking for equally dated threads.
    • Improved active plan state handling when activity history includes unrelated events.
  • Tests & Performance

    • Added broader automated coverage and performance benchmarks for thread sorting, plan selection, and usage formatting.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 10, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at fdc44a7

Macroscope's review found this PR approvable — This is a contained client-side performance refactor that reduces repeated sorting, date parsing, and formatter construction without adding capabilities, changing defaults, or altering interfaces. The benchmark and regression coverage are development-only, while the production edits have bounded performance and memory effects.

No code changes detected at 3183fbc. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 677c3e61-c7ba-42cd-98d7-81deed44a647

📥 Commits

Reviewing files that changed from the base of the PR and between fdc44a7 and ec055a9.

📒 Files selected for processing (1)
  • apps/web/src/session-logic.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The changes optimize thread ordering, plan activity selection, and date-time formatter creation. New tests verify ordering, time-zone behavior, input preservation, and edge cases. A benchmark suite measures the affected operations with deterministic datasets.

Changes

Runtime performance updates

Layer / File(s) Summary
Thread ordering and selection
packages/client-runtime/src/state/threadSort.ts, packages/client-runtime/src/state/threadSort.test.ts
Thread sorting and project-thread selection use native sorting, direct scanning, timestamp precomputation, and short-array fast paths. Tests cover ordering, ties, invalid timestamps, filtering, and reference preservation.
Plan activity ordering
apps/web/src/session-logic.ts, apps/web/src/session-logic.test.ts
deriveActivePlanState filters plan activities before sorting them. Tests cover sequence ordering, ignored activities, duration calculation, input preservation, and null results.
Date-time formatter caching
packages/shared/src/usageFormat.ts, packages/shared/src/usageFormat.test.ts
Date-time formatting functions use a bounded formatter cache. Tests cover multiple time zones, invalid inputs, unknown zones, and system-zone behavior.
Performance benchmark coverage
apps/web/src/performance.bench.ts
Deterministic benchmarks cover thread sorting, project-thread selection, plan derivation, and time-zone-aware usage formatting.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Refactor

Suggested reviewers: juliusmarminge, t3dotgg

Merge Risk: ⚪ Minimal · up to 3183f

The performance changes preserve the covered behavior and no actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: reducing repeated sorting and date-formatting work.
Description check ✅ Passed The description is detailed and relevant. It explains the changes, motivation, benchmarks, verification, testing, and checklist status. The template's separate "Why" heading is omitted, but the ration…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@Bil0000

Bil0000 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed the advisory docstring-coverage warning. This patch adds no public API, preserves the existing behavior, and includes focused tests for the changed paths. Extra docstrings solely to meet a coverage percentage would add noise to this small performance change, so I am leaving that advisory warning unchanged. The CodeRabbit check passed and reported no actionable code findings.

@shivamhwp
shivamhwp merged commit ca6416e into pingdotgg:main Sep 12, 2026
24 checks passed
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 12, 2026
## What's Changed
* feat(settings): add open source license notices by @juliusmarminge in pingdotgg/t3code#8962
* perf(client): reduce repeated sorting and date formatting by @Bil0000 in pingdotgg/t3code#11019
* feat: add inline file previews and attachment chips across surfaces by @chrisdeeming in pingdotgg/t3code#11265
* fix(desktop): preserve long offscreen text in SnapShots by @Bil0000 in pingdotgg/t3code#11250
* perf(server): avoid workspace scans when loading pull requests by @Bil0000 in pingdotgg/t3code#11299


**Full Changelog**: pingdotgg/t3code@v0.0.41-nightly.20260912.1576...v0.0.41-nightly.20260912.1599

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.41-nightly.20260912.1599
aorwall added a commit to aorwall/t3code that referenced this pull request Sep 13, 2026
Merges `upstream/main` at `0c5771d60` into the fork, from merge base
`e81606494` — 32 upstream commits.

The range is mostly client polish, plus two structural changes that
mattered
here: upstream extracted the sidebar header into a new component
(`SidebarThreadHeader.tsx`, pingdotgg#11315), which is where two fork gates had
to be
re-homed, and upstream added a `context` field to orchestration messages
at the
exact anchor the fork's `origin` field sits on, which is four of the
eight
conflicts.

## Merge stats

- Landed (`HEAD^1..HEAD`): 489 files, 36117+/5930−
- Upstream range (base..`HEAD^2`): 484 files, 35784+/5824−
- Fork delta (`HEAD^2..HEAD`): 767 files, 78724+/2528−

The two file lists reconcile exactly. The 5 extra landed files are all
fork-owned and none of them is upstream work:
`apps/web/src/fork/SidebarThreadFilter.tsx`
(one className, described below), `docs/fork/inventory.json`,
`docs/fork/gaps.md`, `docs/fork/upstream-merge-log.md`, and
`.agents/skills/fork-upstream-merge/scripts/unsupported-methods.mjs`.
Nothing in
the upstream range failed to land.

## Conflicts

All 8 were resolved by the verdict `preflight.mjs` printed. No `decide`
conflict
was left unresolved.

- `projector.ts`, `orchestration.ts`, `threadReducer.ts`,
`MessagesTimeline.tsx`
— `converged — message-origin-upstream-files`, and all the same
conflict:
upstream appended where the fork already appends. Both sides kept, twice
per
file for the first three. `duplicate-adds.mjs` confirms no line was
taken
  twice.
- `Sidebar.tsx` — `converged — thread-visibility-upstream-files`. Took
upstream
whole; its `SidebarThreadFilter` import was left unused by the
extraction and
  was removed.
- `SettingsSidebarNav.tsx` — unlisted. Kept the fork's
`settingsPathEnabled`
filter over the personal nav items and took upstream's new active-state
rule
(`/settings/general` stays active on `/settings/open-source-licenses`).
- `ChatComposer.tsx` — unlisted, so `decide, then add an entry`. Both
fork deltas
  survived and the entry is now written; see below.
- `routeTree.gen.ts` — generated; regenerated with
`regen-route-tree.mjs` after
  the install.

`pnpm-lock.yaml` auto-merged rather than conflicting, so it was reset to
`upstream/main` and the fork edges re-derived with `vp i`. The remaining
diff
against upstream is exactly the `@t3tools/moatless-api` workspace link,
`mermaid ^11.17.2`, and one alchemy peer hash.

Two findings worth naming here:

- **A fork gate's host file was replaced by a file upstream had not
written yet.**
  pingdotgg#11315 extracted the whole sidebar header into
`apps/web/src/components/sidebar/SidebarThreadHeader.tsx`. Both fork
deltas
were re-applied there additively — the `FEATURES.projectManagement` gate
on
New project, and `<SidebarThreadFilter />` as a third child of
upstream's new
segmented icon well. No props threaded, no state added, no upstream JSX
re-indented. The one edit outside that file is
`SidebarThreadFilter.tsx`'s
  trigger className, now `size-7` so it matches upstream's own
  `SidebarHeaderIconButton` in the well it now sits in.
- **The unsupported-method derivation could not read the backend, and
that was
the script's fault, not a finding.** `unsupported-methods` exited 2 with
"could
not read the backend dispatch". The Moatless backend moved its dispatch
a
second time: `crates/t3code/src/rpc/dispatch.rs` is now a module stub
over an
`rpc/dispatch/` directory whose `routing.rs` holds the arms and whose
siblings
hold the handler bodies. `BACKEND_APIS` now names the directory and the
script
concatenates every `.rs` file in it — pointing it at `routing.rs` alone
would
have read the arms and lost the handlers, and `refusesInside` only
follows
calls it can find in the same source, so every conditional refusal would
have
  come back as a false DROP.

## Inventory

- `moatless-admin-pages` was stale: it still listed the two Workspaces
admin
routes that the 2026-09-12 commit folded into the project settings page.
Re-pointed to the five surfaces that remain, and the untracked delta
that move
  left behind is now its own entry, `project-workspace-settings`.
- `chat-surface-gates` gained
`apps/web/src/components/chat/ChatComposer.tsx`
with a guard on `FEATURES.accessMode`, plus a `chat-composer-gates` path
policy
so the next merge gets a cached verdict instead of the same decision.
The two
deltas there are the runtime-mode picker lifted into a
`runtimeModePicker`
  const behind the flag, and `phase === "running"` left out of
  `collapsedComposerPrimaryActionDisabled`.
- `inventory-check.mjs` is clean.

## Unsupported methods

0 ADD, 0 DROP, 2 KEEP (`git.preparePullRequestThread`, `vcs.switchRef`),
5 known
exceptions still firing, no stale ones. `packages/contracts/src/rpc.ts`
is
unchanged: the range's one unsupported-surface change is upstream's
Cursor
`--classic` launcher fix, which lands on a method already refused.

## Feature classification

### Usable as-is

Client-side work the fork can expose with no Moatless backend or
deployment
change. 28 of the 32 commits.

- Open-source license notices page (pingdotgg#8962) — new
`/settings/open-source-licenses`
route; upstream also made `/settings/general` stay active while it is
open.
- Client perf: fewer repeated sorts and date formats (pingdotgg#11019).
- Inline file previews and attachment chips across surfaces (pingdotgg#11265) —
rides
  `attachments.createUploadUrl` and `assets.createUrl`, both dispatched.
- Subagent spawns as an expandable work row (pingdotgg#11433) and those rows kept
visible
under folded turns (pingdotgg#11474) — derived from the orchestration event
stream the
  backend already serves.
- Opt-in thread notifications and sounds (pingdotgg#11481) — client settings,
persisted
  through the `server.getSettings` read the backend serves.
- Large pastes folded into text attachments (pingdotgg#11442); user input kept
outside
collapsed work (pingdotgg#11363); each chat message exposed as a heading for
screen
  readers (pingdotgg#11199); the default diff file state (pingdotgg#11484).
- Sidebar project scope folded into the search row (pingdotgg#11315); thread
status icons
completed and input threads kept prominent (pingdotgg#11461); sidebar search and
footer
  spacing (pingdotgg#11466); draft row heights matched to thread rows (pingdotgg#11512).
- Image chips tinted with their average colour (pingdotgg#11468); viewer controls
moved
outside the media with arrow navigation restored (pingdotgg#11470); snapshot
preview size
preserved in sent messages (pingdotgg#11429); preview focus preserved on window
return
  (pingdotgg#11444).
- Unavailable account limits made more visible (pingdotgg#10601) — web-only; the
backend
  dispatches `server.getUsageSummary`.
- Saved environments switched off instead of removed (pingdotgg#11478) — entirely
client-side (connection catalog and registry). This build runs one
environment
and gates the Connections settings page, so nothing on screen changes;
the
  catalog behaviour carries.
- Desktop and mobile: long offscreen text in SnapShots (pingdotgg#11250), native
preview
User-Agent kept for Turnstile (pingdotgg#7110), bounded backend shutdown wait on
quit
(pingdotgg#7599), expo-audio pinned (pingdotgg#11426), photo library picks rendered to a
bounded
JPEG off the JS thread (pingdotgg#11440), launch crash with a PR stack (pingdotgg#11486),
the
  shared-content alert after sending (pingdotgg#11487).
- Repository hygiene: `.pnpm-store/v11` deleted.

### Unsupported in Moatless / needs implementation

- **Cursor links open in classic IDE mode (pingdotgg#11498).** Upstream gave
Cursor
`baseArgs: ["--classic"]` in `packages/contracts/src/editor.ts` so a
file open
  targets the IDE rather than its Agents Window, and tested it in
  `apps/server/src/process/externalLauncher.ts`. The method behind it,
`shell.openInEditor`, is not dispatched — the browser is not on the
machine the
workspace is on — so this lands in the contract and in `apps/server` and
changes nothing here. Recorded in `docs/fork/gaps.md` under _Opening in
an
external editor_, whose standing conclusion is that the surface is a
candidate
  for deletion rather than for serving.

### Backend behavior to consider reproducing in Moatless

All three are recorded in `docs/fork/gaps.md` under _Runtime fixes
upstream made
to its own server_. Nothing in this repository holds them open; they are
Moatless-side work.

- **Listing pull requests should read only the projects asked about
(pingdotgg#11299).**
`listWorkspaceProjects` fetched the whole shell snapshot and filtered
it; it now
  asks the projection for the one project, or for the listed ids
  (`apps/server/src/pullRequest/PullRequestService.ts`,
  `persistence/Layers/ProjectionSnapshotQuery.ts`). Moatless dispatches
`pullRequests.summary`, so the same cost lands on it as soon as a
summary is
  derived from a list.
- **Usage should read each provider account's own history directory
(pingdotgg#11485).**
Upstream resolves an account's home from its home setting or its
`CODEX_HOME` /
`CLAUDE_CONFIG_DIR` / `GROK_HOME` variable, counts disabled accounts,
and
de-duplicates accounts sharing a directory
(`apps/server/src/usage/UsageService.ts`).
Moatless serves `server.getUsageSummary` itself, so an account with a
custom
home reports zero there — or double — until it resolves homes the same
way.
- **Forgejo and Gitea remotes should be first-class source control
(pingdotgg#11436).**
Upstream recognises both hosts and drives them with the `fj` and `tea`
CLIs
  across remote identity, PR creation and PR sync (`git/GitManager.ts`,
  `project/RepositoryIdentityResolver.ts`,
`orchestration/PullRequestSyncReactor.ts`). Moatless owns git and pull
requests,
so a Forgejo or Gitea project is an unrecognised host there regardless
of what
  the client can render.

## Verification

`verify.mjs` is green on seven of eight checks: `duplicate-adds` (none
across 34
files both sides changed), `tripwires` (3 deleted surfaces intact,
exactly the 5
known re-deletions, 3 allowed workflows), `resolution-check` (16
fork-delta paths
still differ from upstream, 17 carry upstream's change, 17
theirs-verbatim
byte-identical, 18 unlisted), `unsupported-methods`, `fmt:check`,
`lint`,
`typecheck`.

`test` is red on one file, and it is the standing environmental failure
rather
than a merge regression:

- `@t3tools/desktop` → `scripts/browser-secret-native.test.mjs > bundled
libsecret
helper` fails with `Package 'libsecret-1' not found` from `pkg-config`.
1 file
  of 105; the rest of the package is 1341 tests passed. The test file is
byte-identical to upstream, arrived on the fork before this merge, and
the
sandbox image ships neither `libsecret-1` nor its pkg-config file. There
is no
  root in the sandbox, so it cannot be installed here. Recorded in
`docs/fork/gaps.md` under _The desktop suite needs libsecret, which the
sandbox
  does not have_.

Four packages did not finish under `vp run -r test` and were each run
alone
again, all green: `@t3tools/mobile` (165 files, 1528 tests), `t3` (317
files,
4528 tests), `@t3tools/web` (412 files, 5205 tests), `t3code-relay` (30
files,
284 tests).

The owned-concern sweep over newly added upstream files found no keyword
hits, so
no `concerns` entry was needed.

**CI caught one thing no local check runs.** `Build & push moatless-t3`
failed on
the first push: upstream's new `t3code:third-party-licenses` plugin
(pingdotgg#8962) runs
in `generateBundle` and refuses any bundled package whose license it
cannot
resolve, and three packages reach the web bundle only through the fork's
own
`mermaid` edge — `khroma` via mermaid, `fastdom` and `strictdom` via
cytoscape
under it — so upstream's config has never carried overrides for them.
Fixed with
three `packageOverrides` entries: `khroma` needed a `license: "MIT"`
declaration
only, since it ships its own `license` file, and `fastdom` and
`strictdom` needed
a `generatedNotice` each, since both declare MIT and ship no notice
file.
Verified with the build itself — all three now appear in
`apps/web/dist/third-party-licenses.json` with a license and a notice,
and the
workflow is green. The delta is held by the `mermaid-diagrams` inventory
entry
plus a `third-party-licenses-config` path policy, and the reason it
escaped
`verify.mjs` — which has no build step at all — is now
`docs/fork/gaps.md`, _Nothing builds the web app before a merge is
pushed_.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 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.

2 participants