Skip to content

perf(web): long composer code blocks no longer re-highlight on every keystroke - #19439

Merged
juliusmarminge merged 5 commits into
pingdotgg:mainfrom
MohibShaikh:perf/web-composer-highlight-after-typing
Oct 20, 2026
Merged

juliusmarminge merged 5 commits into
pingdotgg:mainfrom
MohibShaikh:perf/web-composer-highlight-after-typing

Conversation

@MohibShaikh

@MohibShaikh MohibShaikh commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Typing in a long composer code fence synchronously re-tokenizes the entire fence on each edit, stalling the web renderer. Fences above 2,000 characters now keep their mapped colors and refresh after a 300 ms pause. Short fences remain immediate; the existing 20,000-character cap remains.

Retains @MohibShaikh's original work and measurements in #19439, with main merged and the tests and implementation simplified. The takeover also prevents stale async loads from bypassing the delay and refreshes immediately when the theme changes. Split token decorations stay inside their fences; failed grammar loads clear retained colors.

Tradeoff: retained colors can be briefly incorrect until a pause. Edits elsewhere also restart that pause, and changing a long fence's language can retain its old colors for 300 ms. During a first grammar load, editing elsewhere can defer initial colors until the pause.

Validation at 8e9f96ee72:

  • Original suite: 12 tests → 14 in the same existing file; eight baseline tests retained. The separate test rewrite confirmed three regressions fail against main, then pass with the fix. Two additional tests protect the stale-load and theme fixes.
  • 76 related tests passed (composerCodeBlockHighlight.test.ts and composer-code-block.test.ts), plus changed-file lint/formatting and web typecheck.
  • One parallel adversarial review round, Claude Opus 5.5 and Codex GPT-6 Sol: no blocking findings. Optional first-load scheduling suggestions were assessed and left within the bounded pause behavior above.
  • Headless Linux Browser panel, isolated server, real composer and neutral 16,659-character TSX fixture. Twelve editor transactions dispatched on an 80 ms interval: main made 12 tokenization calls (480 ms total in Shiki); this head made zero during editing, then one after the pause (45 ms). Time to dispatch the 12 edits was 2,895 ms → 1,352 ms. These are single-run diagnostic measurements, not physical-keyboard input latency statistics. Both ended with 4,081 colored spans. A short fence still re-tokenized immediately.

Before — main, same composer fixture:

Before: long TSX fence in the real composer

After — current head, matching fixture and viewport:

After: long TSX fence retains syntax colors

Recordings: before: per-edit tokenization, after: deferred tokenization. A local DejaVu font fixture made captures legible; no provider turn was started.

The change applies to the web composer and the renderer used by desktop. Native mobile uses a separate composer. No provider adapters, wire contracts, permissions, or connection routing change. Not verified: macOS, Windows, iOS, Android, Electron desktop, Safari, Firefox, remote relay/tunnel, or a separate physical client.

Implementation: Codex GPT-6.1 Sol through T3 Code; tests: Codex GPT-6 Astra; independent reviews: Claude Opus 5.5 and Codex GPT-6 Sol.

…keystroke

Every edit changes a code block's signature, so the whole fence was
re-tokenized with Shiki, synchronously, before the next paint. In an
18,000-character TSX fence the median keystroke took 200-248 ms to paint.

A fence over 2,000 characters now keeps the colors it had, mapped through
the edit and limited to its block, and is re-tokenized once typing pauses
for 300 ms. Shorter fences still repaint on every keystroke. A highlighter
that fails to load clears the old colors instead of keeping them.
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 14, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 4c90270

Macroscope's review found this PR approvable — This is a localized performance fix that debounces highlighting only for long composer code blocks while preserving existing behavior for short blocks and final syntax coloring. Focused tests cover the new timing and decoration-preservation paths, with no schema, deployment, security, billing, or static-analysis configuration impact.

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

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

Only developers with an assigned seat can use this organization's usage-based review budget, and seats here are assigned manually. Ask an admin to assign a seat, or change the review continuation mode in Billing.

Next included review available in 3 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 353a2ddb-c109-4fc5-abc8-1dda811f8b87
📥 Commits

Reviewing files that changed from the base of the PR and between 4c90270 and 93d14e9.

📒 Files selected for processing: 2
  • apps/web/src/components/composerCodeBlockHighlight.test.ts
  • apps/web/src/components/composerCodeBlockHighlight.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 140b8c88-5a1c-487d-ba4b-f861d0c7c5b6
📥 Commits

Reviewing files that changed from the base of the PR and between 7371925 and 4c90270.

📒 Files selected for processing: 2
  • apps/web/src/components/composerCodeBlockHighlight.test.ts
  • apps/web/src/components/composerCodeBlockHighlight.ts

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

📝 Walkthrough

The highlighting plugin now defers tokenization for long code blocks during edits, retains existing decorations until refresh, and clears decorations when a highlighter load fails.

Changes: Code-block highlighting

Layer / File(s) Summary
Schedule deferred highlighting
apps/web/src/components/composerCodeBlockHighlight.ts, apps/web/src/components/composerCodeBlockHighlight.test.ts
The plugin defers tokenization for blocks over 2,000 characters until 300 ms after typing stops. It excludes blocks over 20,000 characters, handles highlighter-load failures with empty decorations, and cancels pending refreshes when the view is destroyed. Tests cover deferred and immediate tokenization, and failed highlighter loads.
Retain decorations across edits
apps/web/src/components/composerCodeBlockHighlight.ts, apps/web/src/components/composerCodeBlockHighlight.test.ts
The plugin maps decorations through transactions and retains them within their code block while a new highlight is pending. It clips decorations that cross block boundaries. A test checks decoration placement after a fence split.

Priority: ⬇️ Low

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

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant EditorView
  participant CodeBlockHighlightPlugin
  participant DeferredRefreshTimer
  participant Highlighter
  EditorView->>CodeBlockHighlightPlugin: Apply editor update and defer long-block refresh
  CodeBlockHighlightPlugin->>DeferredRefreshTimer: Schedule refresh after 300 ms
  DeferredRefreshTimer->>CodeBlockHighlightPlugin: Trigger deferred refresh
  CodeBlockHighlightPlugin->>Highlighter: Tokenize eligible blocks
  Highlighter-->>CodeBlockHighlightPlugin: Return decorations or empty decorations
Loading

Suggested reviewers: chrisdeeming

Merge Risk: ⚪ Minimal · up to 4c902

The deferred highlighting change appears mergeable after normal checks; no outstanding issue was identified.

Pre-merge checks: Passed 4
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 is concise, specific, and accurately summarizes the main performance change for long composer code blocks.
Description check Passed The description clearly explains the problem, implementation, tradeoffs, scope, verification results, test coverage, visual evidence, and unverified environments. It is substantially complete and dire…
Full details: Description check

Explanation

The description clearly explains the problem, implementation, tradeoffs, scope, verification results, test coverage, visual evidence, and unverified environments. It is substantially complete and directly related to the change.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@juliusmarminge juliusmarminge self-assigned this Oct 19, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Taking over this one as maintainer for the composer-chat-14 batch. I will preserve the long-fence typing fix, refresh it against main, simplify the tests and implementation, and verify it with independent reviews. Thanks @MohibShaikh for the reproduction and measurements.

@juliusmarminge
juliusmarminge merged commit 0979221 into pingdotgg:main Oct 20, 2026
25 checks passed
@MohibShaikh
MohibShaikh deleted the perf/web-composer-highlight-after-typing branch October 20, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants