Skip to content

perf(mobile): highlight source files in small batches that keep grammar state - #16729

Merged
juliusmarminge merged 2 commits into
t3code/mobile-perf/reuse-settled-sortfrom
t3code/mobile-perf/carry-source-highlight-state
Oct 8, 2026
Merged

juliusmarminge merged 2 commits into
t3code/mobile-perf/reuse-settled-sortfrom
t3code/mobile-perf/carry-source-highlight-state

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Scope and approval

This is maintainer-requested work from the mobile performance audit, explicitly authorized as one measured fix per PR. It also fixes an existing syntax-coloring bug: restarting grammar state at batch boundaries incorrectly colors multiline comments and strings. Scope is limited to the mobile highlighter and its regression test.

The mobile source viewer highlights a whole file in highlightLines, in batches of 200 lines. Each batch started from a fresh grammar state. A block comment, template string, or other multi-line construct that crossed a batch boundary was therefore colored wrong after the boundary. The 200-line batches were also not bounded by size. On a 3,980-line file, one stretch of work between yields took about 110 ms.

highlightLines now tokenizes batches of at most 2,000 characters. Each batch resumes from the previous batch's getLastGrammarState, the same approach nativeReviewDiffHighlighter and incrementalSnippet already use. Batches run back to back, and the loop yields with the existing waitForNextFrame only after about 16 ms of work. Yielding after every batch would cost a timer per batch. An earlier variant that did this took twice as long in total (1.46 s), so we dropped it. Lines over 1,000 characters still fall back to plain tokens. A skipped line resets the grammar state, because its ending state is unknown. Plain-text files get no grammar state from Shiki, so they behave as before. Callers are unchanged: source and attachment files, the highlightCodeSnippet fallback path, and selected-line highlighting for review comments.

Measurement

All runs open the same 3,980-line, 167,125-character TypeScript file on an iOS simulator release build (Hermes, no Metro). The native binary is identical across runs; only the JS bundle is swapped. Before is e1017c4243 (#16369 head) and after is a1531757d9.

Diagnostic bundles. These used separate builds with a small patch (before, after, never shipped) that times each JS slice of highlightLines between awaits.

Run Slices Total (ms) Work (ms) Waits (ms) Longest slice (ms) Median slice (ms)
before 1 20 697.8 680.9 17.0 117.1 28.9
before 2 20 664.8 658.1 6.4 107.8 28.5
after 1 30 684.1 658.8 24.7 81.4 20.6
after 2 30 671.9 654.2 17.6 80.3 19.9

The longest uninterrupted highlighter slice fell from about 108–117 ms to about 80 ms, and the median slice fell from about 29 ms to about 20 ms. Total work and total time are unchanged. Median waits were 0.1–0.2 ms, so the extra yields add no idle time. The remaining 80 ms slice is the second slice, in both runs. It is probably the first-use cost of compiling the grammar's regular expressions, which batching cannot split.

Production bundles (no instrumentation), run as before, after, after, before. The highlight span, from first to last sampled highlighter work, was 0.719 s and 0.666 s before, and 0.671 s and 0.676 s after. Total time does not improve.

The production profiler also reports "busy runs": runs of consecutive non-idle 1.5 ms samples. Back-to-back JS tasks merge into one run, so these are not task durations. They cannot show the slice change, and an earlier read of them as a task-length win was wrong. That is why the diagnostic builds above exist.

This measures how long the highlighter holds the JS thread at a stretch. It does not measure input latency, dropped frames, battery, or a physical device. The benefit is modest: the JS thread gets a chance to handle touches and renders between shorter highlighting slices on large files. The color fix is the larger correctness change. Aggregate data (no traces, logs, or client data): source-highlight-evidence.json.

Screenshots are not applicable. The visible change is the color of multi-line constructs past line 200, which the test below covers.

Tests

shikiReviewHighlighter.test.ts adds "keeps colors for a block comment that spans highlight batches". It fails on the base implementation and passes here. The highlighter, engine, and incremental-snippet tests pass (3 files, 17 tests).

Opus 5.5 via Claude Code in T3 Code.

🤖 Generated with Claude Code


Devin Review

Native before/after proof

Same Release native binary and synthetic 240-line file. Before, lines 201–214 inside the block comment incorrectly receive code colors. After, the comment stays gray through its closing delimiter; code coloring resumes at line 215. These images and recordings demonstrate correctness, not input latency.

Before After
Before: comment loses its coloring after line 200 After: comment coloring is preserved

Before scrolling recording · After scrolling recording

@juliusmarminge
juliusmarminge added this pull request to stack #15249 October 7, 2026 05:26
@juliusmarminge juliusmarminge changed the title t3code/mobile perf/carry source highlight state perf(mobile): highlight source files in small batches that keep grammar state Oct 7, 2026
@juliusmarminge
juliusmarminge marked this pull request as ready for review October 7, 2026 05:26
@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Oct 7, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 7, 2026
@github-actions github-actions Bot added the size:M 30-99 changed lines (additions + deletions). label Oct 7, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at a153175

Macroscope's review found this PR approvable — This is a contained mobile syntax-highlighting fix and performance optimization: tokenization is character-bounded, multiline grammar state is preserved, and long-file work yields more predictably. It adds a focused regression test without changing APIs, product configuration, schemas, or sensitive functionality.

No code changes detected at 4902bc3. Prior analysis still applies.

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

@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 46fa887c-e6ad-4d4d-85a7-5ee85eaf1208
📥 Commits

Reviewing files that changed from the base of the PR and between 8b70cca and 4902bc3.

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: e976b0cb-14de-4d66-9fd2-57fcc8858385
📥 Commits

Reviewing files that changed from the base of the PR and between e1017c4 and a153175.

📒 Files selected for processing (2)
  • apps/mobile/src/features/review/shikiReviewHighlighter.test.ts
  • apps/mobile/src/features/review/shikiReviewHighlighter.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The review highlighter now processes source lines in character-bounded batches and carries grammar state between batches. It resets that state after long lines and yields to the next frame based on elapsed time. A test checks token colors in a block comment that spans batches.

Changes

Review highlighting

Layer / File(s) Summary
Character-bounded highlighting and grammar state
apps/mobile/src/features/review/shikiReviewHighlighter.ts, apps/mobile/src/features/review/shikiReviewHighlighter.test.ts
highlightLines uses a 2,000-character batch limit and carries grammar state between batches. Lines over 1,000 characters receive plain-text tokens and reset grammar state. For inputs over eight lines, it yields to the next frame when work remains and at least 16 ms have elapsed since the prior yield. The test checks token colors in a block comment spanning batches.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: t3dotgg

Merge Risk: ⚪ Minimal · up to a1531

This change improves highlighting accuracy across batch boundaries and slightly reduces long highlighting slices. No concrete merge-blocking risk was found.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main changes: smaller highlighting batches and preserved grammar state. It is concise and uses a conventional commit format.
Description check ✅ Passed The description provides detailed problem context, implementation changes, approval scope, measurements, verification results, limitations, and visual evidence. It does not use the required Problem an…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 5.0 KiB — 6.8 KiB ✅
Codex Thread snapshot wire — 3.8 KiB — 4.9 KiB ✅
Codex Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Codex Live turn WebSocket decoded — 20.9 KiB — 29.3 KiB ✅
Codex Live turn messages — 2 — 8 ✅
Claude Total thread wire — 5.0 KiB — 6.8 KiB ✅
Claude Thread snapshot wire — 3.8 KiB — 4.9 KiB ✅
Claude Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Claude Live turn WebSocket decoded — 21.2 KiB — 29.3 KiB ✅
Claude Live turn messages — 1 — 8 ✅

Baseline: unavailable · PR result: 4902bc3 · Source CI: success

Scenario and decoded snapshot size

10 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.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@juliusmarminge
juliusmarminge force-pushed the t3code/mobile-perf/carry-source-highlight-state branch from a153175 to 889a131 Compare October 8, 2026 05:25
@juliusmarminge
juliusmarminge force-pushed the t3code/mobile-perf/carry-source-highlight-state branch 2 times, most recently from 8b9c1c9 to 8b70cca Compare October 8, 2026 05:39
juliusmarminge and others added 2 commits October 7, 2026 22:40
…ar state

Opening a large source file tokenized it in 200-line batches. On a 3.2k-line
file each batch ran 25-260ms on the JS thread, and batches restarted from an
empty grammar state, so a comment or template string crossing a boundary lost
its colors. Batches are now capped at 2,000 characters and resume from the
previous batch's grammar state, matching the native review diff highlighter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Yielding after every 2,000-character batch kept each JS task short but waited
for a frame after each one, which about doubled the time until a large file got
its colors. Batches now run back to back until about 16ms of work has passed,
then yield once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/mobile-perf/carry-source-highlight-state branch from 8b70cca to 4902bc3 Compare October 8, 2026 05:41
@juliusmarminge
juliusmarminge merged commit 33afd5a into main Oct 8, 2026
35 of 37 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/mobile-perf/carry-source-highlight-state branch October 8, 2026 05:49
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Oct 8, 2026
## What's Changed
* chore(review): remove the custom Approvability check by @esthor in pingdotgg/t3code#17018
* perf(mobile): show the cached thread list sooner and stop the freeze after it by @juliusmarminge in pingdotgg/t3code#16713
* fix(server): replaying a command no longer freezes the server by @SunkenInTime in pingdotgg/t3code#17041
* fix(server): opening a long thread no longer blocks the server for a second by @SunkenInTime in pingdotgg/t3code#17029
* fix(server,web,mobile): thread links reference the thread id, not a baked title by @juliusmarminge in pingdotgg/t3code#17017
* fix(web): messages with quotes or links no longer collapse when short by @flamboh in pingdotgg/t3code#16624
* refactor(web): PR loading skeleton shares the detail panel's layout by @flamboh in pingdotgg/t3code#15583
* fix(web): PR file headers keep the file name in narrow panels by @flamboh in pingdotgg/t3code#15562
* fix(web): PR timeline no longer shifts when its scrollbar appears by @flamboh in pingdotgg/t3code#15588
* fix(server): held queued wakes no longer keep delegated tasks running by @juliusmarminge in pingdotgg/t3code#17028
* fix(server): a message sent during a rollback no longer undoes it by @t3dotgg in pingdotgg/t3code#17079
* fix(web): PR file stats ignore the hide-whitespace toggle by @flamboh in pingdotgg/t3code#16162
* perf(mobile): omit duplicated turn items from bounded thread snapshots by @juliusmarminge in pingdotgg/t3code#15385
* perf(mobile): pause elapsed-time timers on hidden thread screens by @juliusmarminge in pingdotgg/t3code#15397
* fix(mobile): pause hidden home thread list updates by @juliusmarminge in pingdotgg/t3code#15705
* fix(mobile): restore file viewer insets and glass header by @juliusmarminge in pingdotgg/t3code#17073
* fix(web): PR code toolbar no longer overlaps in narrow panels by @flamboh in pingdotgg/t3code#15561
* fix(web): PR commit menu no longer stretches across the window by @flamboh in pingdotgg/t3code#15560
* fix(web): command palette scrollbar no longer clipped at the top by @flamboh in pingdotgg/t3code#17035
* fix(web): Usage breadcrumb stays centered on small viewports by @flamboh in pingdotgg/t3code#15552
* fix(mobile): stop refreshing Git status on streamed thread updates by @juliusmarminge in pingdotgg/t3code#15893
* fix(mobile): skip move indexes for empty and single-thread sections by @juliusmarminge in pingdotgg/t3code#16115
* perf(mobile): reuse encoded rows in shell cache saves by @juliusmarminge in pingdotgg/t3code#16129
* perf(mobile): skip showcase subscriptions in normal builds by @juliusmarminge in pingdotgg/t3code#16131
* perf(mobile): remove unused Home project sorting by @juliusmarminge in pingdotgg/t3code#16177
* perf(mobile): reduce move-menu index allocations by @juliusmarminge in pingdotgg/t3code#16256
* perf(mobile): skip impossible thread-key lookups by @juliusmarminge in pingdotgg/t3code#16263
* fix(mobile): collect UI runtime garbage on iOS memory warnings by @juliusmarminge in pingdotgg/t3code#16296
* fix(mobile): stop Git sheet refresh loop by @juliusmarminge in pingdotgg/t3code#16305
* fix(mobile): refresh Git status after reconnect by @juliusmarminge in pingdotgg/t3code#16329
* fix(mobile): update the iOS Git header menu when status changes by @juliusmarminge in pingdotgg/t3code#16330
* perf(mobile): reuse the settled sort when settled rows are unchanged by @juliusmarminge in pingdotgg/t3code#16369
* perf(mobile): highlight source files in small batches that keep grammar state by @juliusmarminge in pingdotgg/t3code#16729
* feat(web): quote chips show what you said about the quote by @flamboh in pingdotgg/t3code#15703
* feat(web): projectless threads show their machine in the sidebar by @flamboh in pingdotgg/t3code#17022
* fix(lineage): keep agent effort and speed after completion by @Bil0000 in pingdotgg/t3code#16925
* fix(mobile): align diff scrolling with glass headers by @juliusmarminge in pingdotgg/t3code#17085
* fix(web): workspace page headers can no longer grow past the top-bar height by @maria-rcks in pingdotgg/t3code#17086
* feat(mobile): redesign the Add environment sheet by @juliusmarminge in pingdotgg/t3code#17092


**Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261008.2801...v0.0.46-nightly.20261008.2813

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261008.2813
github-actions Bot added a commit to davidvanderklay/t3code-flake that referenced this pull request Oct 8, 2026
## What's Changed
* chore(review): remove the custom Approvability check by @esthor in pingdotgg/t3code#17018
* perf(mobile): show the cached thread list sooner and stop the freeze after it by @juliusmarminge in pingdotgg/t3code#16713
* fix(server): replaying a command no longer freezes the server by @SunkenInTime in pingdotgg/t3code#17041
* fix(server): opening a long thread no longer blocks the server for a second by @SunkenInTime in pingdotgg/t3code#17029
* fix(server,web,mobile): thread links reference the thread id, not a baked title by @juliusmarminge in pingdotgg/t3code#17017
* fix(web): messages with quotes or links no longer collapse when short by @flamboh in pingdotgg/t3code#16624
* refactor(web): PR loading skeleton shares the detail panel's layout by @flamboh in pingdotgg/t3code#15583
* fix(web): PR file headers keep the file name in narrow panels by @flamboh in pingdotgg/t3code#15562
* fix(web): PR timeline no longer shifts when its scrollbar appears by @flamboh in pingdotgg/t3code#15588
* fix(server): held queued wakes no longer keep delegated tasks running by @juliusmarminge in pingdotgg/t3code#17028
* fix(server): a message sent during a rollback no longer undoes it by @t3dotgg in pingdotgg/t3code#17079
* fix(web): PR file stats ignore the hide-whitespace toggle by @flamboh in pingdotgg/t3code#16162
* perf(mobile): omit duplicated turn items from bounded thread snapshots by @juliusmarminge in pingdotgg/t3code#15385
* perf(mobile): pause elapsed-time timers on hidden thread screens by @juliusmarminge in pingdotgg/t3code#15397
* fix(mobile): pause hidden home thread list updates by @juliusmarminge in pingdotgg/t3code#15705
* fix(mobile): restore file viewer insets and glass header by @juliusmarminge in pingdotgg/t3code#17073
* fix(web): PR code toolbar no longer overlaps in narrow panels by @flamboh in pingdotgg/t3code#15561
* fix(web): PR commit menu no longer stretches across the window by @flamboh in pingdotgg/t3code#15560
* fix(web): command palette scrollbar no longer clipped at the top by @flamboh in pingdotgg/t3code#17035
* fix(web): Usage breadcrumb stays centered on small viewports by @flamboh in pingdotgg/t3code#15552
* fix(mobile): stop refreshing Git status on streamed thread updates by @juliusmarminge in pingdotgg/t3code#15893
* fix(mobile): skip move indexes for empty and single-thread sections by @juliusmarminge in pingdotgg/t3code#16115
* perf(mobile): reuse encoded rows in shell cache saves by @juliusmarminge in pingdotgg/t3code#16129
* perf(mobile): skip showcase subscriptions in normal builds by @juliusmarminge in pingdotgg/t3code#16131
* perf(mobile): remove unused Home project sorting by @juliusmarminge in pingdotgg/t3code#16177
* perf(mobile): reduce move-menu index allocations by @juliusmarminge in pingdotgg/t3code#16256
* perf(mobile): skip impossible thread-key lookups by @juliusmarminge in pingdotgg/t3code#16263
* fix(mobile): collect UI runtime garbage on iOS memory warnings by @juliusmarminge in pingdotgg/t3code#16296
* fix(mobile): stop Git sheet refresh loop by @juliusmarminge in pingdotgg/t3code#16305
* fix(mobile): refresh Git status after reconnect by @juliusmarminge in pingdotgg/t3code#16329
* fix(mobile): update the iOS Git header menu when status changes by @juliusmarminge in pingdotgg/t3code#16330
* perf(mobile): reuse the settled sort when settled rows are unchanged by @juliusmarminge in pingdotgg/t3code#16369
* perf(mobile): highlight source files in small batches that keep grammar state by @juliusmarminge in pingdotgg/t3code#16729
* feat(web): quote chips show what you said about the quote by @flamboh in pingdotgg/t3code#15703
* feat(web): projectless threads show their machine in the sidebar by @flamboh in pingdotgg/t3code#17022
* fix(lineage): keep agent effort and speed after completion by @Bil0000 in pingdotgg/t3code#16925
* fix(mobile): align diff scrolling with glass headers by @juliusmarminge in pingdotgg/t3code#17085
* fix(web): workspace page headers can no longer grow past the top-bar height by @maria-rcks in pingdotgg/t3code#17086
* feat(mobile): redesign the Add environment sheet by @juliusmarminge in pingdotgg/t3code#17092


**Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261008.2801...v0.0.46-nightly.20261008.2813

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

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:M 30-99 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