Repository navigation
perf(mobile): highlight source files in small batches that keep grammar state - #16729
Conversation
ApprovabilityVerdict: Approved at 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 You can add or adjust custom eligibility rules. Learn more. |
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe 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. ChangesReview highlighting
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to 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)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: 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. |
a153175 to
889a131
Compare
8b9c1c9 to
8b70cca
Compare
…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>
8b70cca to
4902bc3
Compare
## 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
## 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
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.highlightLinesnow tokenizes batches of at most 2,000 characters. Each batch resumes from the previous batch'sgetLastGrammarState, the same approachnativeReviewDiffHighlighterandincrementalSnippetalready use. Batches run back to back, and the loop yields with the existingwaitForNextFrameonly 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, thehighlightCodeSnippetfallback 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 isa1531757d9.Diagnostic bundles. These used separate builds with a small patch (before, after, never shipped) that times each JS slice of
highlightLinesbetween awaits.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.tsadds "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
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 scrolling recording · After scrolling recording