fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine - #8360

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine
Sep 2, 2026
Merged

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine#8360
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine

Conversation

@LetZico

@LetZicoLetZico commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Opening a large file in the file preview panel could freeze the whole window until force-quit: Shiki's JavaScript regex engine backtracks catastrophically on some ordinary source lines (repro in #8356 is a 66-char Go comment), and one catastrophic match is uninterruptible — the editor's synchronous tokenizer then pins the renderer main thread indefinitely (12+ min at ~200% CPU observed). In the read-only path the same input instead leaves the diff workers spinning forever and highlighting never renders — the symptom in #3884.

Fix: select shiki-wasm (Oniguruma) at every site that can create a highlighter — the shared singleton (syntaxHighlighting.ts, now a PREFERRED_HIGHLIGHTER constant), the diff worker pool, and each component options object. All must agree because the singleton is first-caller-wins and FileRenderer.initializeHighlighter() falls back to the library's shiki-js default. Same grammars and themes, so no visual change; the WASM engine loads as a lazy ~600 KB chunk (CSP already allows 'wasm-unsafe-eval'). The repro file (476 KB, 10,915-line Go) tokenizes in 377 ms on WASM vs a >120 s hang on the JS engine. Superset of #3885, which switches only the worker pool; credit to it for the approach.

Fixes#8356. Fixes#3884 (worker spin reproduced on macOS: four DedicatedWorkers at 100% CPU each, cleared by this change).

Before (main): file renders with no highlighting, worker pool at ~400% CPU; the editable path freezes the tab ("Page Unresponsive"):

before: no highlighting, workers spinning

After: same file, highlighted, UI interactive:

after: highlighted and responsive

Tests

  • pnpm -C apps/web typecheck (tsgo) — clean
  • pnpm -C apps/web test — 281 files, 2,859 tests pass
  • pnpm lint — no errors, none in changed files
  • pnpm -C apps/web build — succeeds; WASM engine emitted as lazy chunk
  • A/B on two dev instances at the same base commit with the [Bug]: File preview panel highlights large files synchronously on the renderer main thread #8356 repro file: pre-fix renders unhighlighted with workers spinning (or freezes the tab via the editable path); with the fix it renders highlighted and stays interactive (screenshots above)

Built with Claude Fable 5 in Claude Code.


Note

Medium Risk
Touches every syntax-highlighting and diff render path; wrong or partial rollout could leave mixed engines on the singleton, but the change is narrowly scoped to preferredHighlighter with no auth or data-handling impact.

Overview
Fixes main-thread freezes and diff-worker CPU spin by standardizing on the Oniguruma WASM highlighter (shiki-wasm) instead of Shiki’s JS regex engine, which could hang indefinitely on some source lines.

Introduces PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts (replacing "shiki-js" in getSharedHighlighter) and threads preferredHighlighter: PREFERRED_HIGHLIGHTER through every Pierre diffs entry point: the diff worker pool, diff panel, file preview (read-only and editable), chat turn/review diffs, PR code tab, and settings font diff preview SSR.

No intended visual change (same themes/grammars); the WASM bundle loads lazily. All sites must agree because the shared highlighter is first-caller-wins.

Reviewed by Cursor Bugbot for commit 6384d96. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Switch syntax highlighter to shiki-wasm to stop UI freezes

  • Adds PREFERRED_HIGHLIGHTER = "shiki-wasm" constant to syntaxHighlighting.ts to switch to the Oniguruma WASM engine.
  • Passes preferredHighlighter: PREFERRED_HIGHLIGHTER into file, diff, and PR code viewer options across the web app.
  • Behavioral Change: All syntax highlighting across the web app switches from the previous default engine to shiki-wasm.

Macroscope summarized 6384d96.

@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20b257ea-6a69-402e-b1cb-f34e41e65fee

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

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 27, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new "every creation site must pass this value" invariant is not applied to the remaining highlighter creation site in apps/web/src/components/settings/SettingsFontPreviews.tsx. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +10 to +18
/**
* Always highlight with the Oniguruma WASM engine. The JavaScript regex engine
* can backtrack catastrophically on ordinary source lines (a Go comment
* containing `{}` and a non-ASCII dash pinned the renderer main thread for 12+
* minutes; the same input tokenizes in under 10ms on WASM). The shared
* highlighter is a first-caller-wins singleton, so every creation site must
* pass this value.
*/
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant documented here ("first-caller-wins singleton, so every creation site must pass this value") is not applied everywhere: apps/web/src/components/settings/SettingsFontPreviews.tsx:80-83 still calls preloadPatchFile({ patch: DIFF_PREVIEW_PATCH, options: { diffStyle: "unified", theme } }), and its own comment above states that pipeline "always awaits the shared highlighter". Appearance settings can easily be opened before any diff/file surface mounts, in which case that call wins the race and pins the JS regex engine for the whole session — exactly the hang this PR fixes.

Suggested smallest fix: add preferredHighlighter: PREFERRED_HIGHLIGHTER to the options passed to preloadPatchFile. If the SSR options type does not accept it, please note in this comment that the SSR path builds its own highlighter rather than the shared singleton, so the invariant stated here is accurate.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 5e5651cpreloadPatchFile reaches DiffHunksRenderer.initializeHighlighter(), which does create the shared singleton with the shiki-js default, so this was a real missed site. preferredHighlighter: PREFERRED_HIGHLIGHTER is now passed there too (the SSR options type accepts it). Typecheck and the 2,859 apps/web unit tests pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

macroscopeapp[bot]
macroscopeappBot previously approved these changes Aug 27, 2026
@macroscopeapp

macroscopeappBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The patch changes the highlighter implementation for every existing file, diff, preview, and worker path, adding a lazy WASM runtime dependency and altering application-wide tokenization behavior. The code is small and consistently wired, but the production blast radius warrants human review.

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

@macroscopeapp
macroscopeappBot dismissed their stale reviewAugust 27, 2026 06:44

Dismissing prior approval to re-evaluate 5e5651c

LetZicoand others added 2 commits September 1, 2026 16:07
…engine
The JavaScript regex engine backtracks catastrophically on ordinary source
lines. A 66-character Go comment line (containing braces and a non-ASCII
dash) pinned the renderer main thread for 12+ minutes when the file preview
panel restored a 476 KB / 10,915-line file: the editor's synchronous
EditorTokenizer #buildStateStack loop calls tokenizeLine per line, and its
500 ms per-line limit cannot fire because vscode-textmate only checks the
clock between regex matches — a single catastrophic match is uninterruptible.
The whole window froze until force-quit (pingdotgg#8356).
The same content tokenizes in 377 ms end-to-end on the Oniguruma WASM engine
(worst single line 9.1 ms, measured via @pierre/diffs getSharedHighlighter on
both engines). The shared highlighter is a first-caller-wins singleton, so
every creation site must name the engine: the app's getSharedHighlighter call,
the diff worker pool options, and each component options object whose
FileRenderer can lazily initialize the singleton with the library's shiki-js
default. PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts names the decision
once.
The workers in DiffWorkerPoolProvider previously ran shiki-js as well, which
plausibly explains the runnable-forever DedicatedWorkers in pingdotgg#3884.
Verified: tsgo --noEmit clean; 2859 apps/web unit tests pass; production
build emits the WASM engine as a lazy ~600 KB chunk; in the dev app the full
repro file tokenizes in 385 ms in-browser where the JS engine previously hung
indefinitely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e comment
Macroscope review: SettingsFontPreviews' preloadPatchFile call reaches
DiffHunksRenderer.initializeHighlighter, which creates the shared
first-caller-wins singleton with the library's shiki-js default. Opening
Appearance settings before any diff/file surface would pin the JS regex
engine for the whole session. Pass PREFERRED_HIGHLIGHTER there too, and
trim the incident detail out of the constant's doc comment.
@t3dotgg
t3dotggforce-pushed the fix/highlighter-wasm-engine branch from 5e5651c to 6384d96CompareSeptember 1, 2026 23:09
@t3dotgg
t3dotgg merged commit feb3ea7 into pingdotgg:mainSep 2, 2026
25 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants

@LetZico@jetersen@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine - #8360

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine
Sep 2, 2026
Merged

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine#8360
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine

Conversation

@LetZico

@LetZicoLetZico commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Opening a large file in the file preview panel could freeze the whole window until force-quit: Shiki's JavaScript regex engine backtracks catastrophically on some ordinary source lines (repro in #8356 is a 66-char Go comment), and one catastrophic match is uninterruptible — the editor's synchronous tokenizer then pins the renderer main thread indefinitely (12+ min at ~200% CPU observed). In the read-only path the same input instead leaves the diff workers spinning forever and highlighting never renders — the symptom in #3884.

Fix: select shiki-wasm (Oniguruma) at every site that can create a highlighter — the shared singleton (syntaxHighlighting.ts, now a PREFERRED_HIGHLIGHTER constant), the diff worker pool, and each component options object. All must agree because the singleton is first-caller-wins and FileRenderer.initializeHighlighter() falls back to the library's shiki-js default. Same grammars and themes, so no visual change; the WASM engine loads as a lazy ~600 KB chunk (CSP already allows 'wasm-unsafe-eval'). The repro file (476 KB, 10,915-line Go) tokenizes in 377 ms on WASM vs a >120 s hang on the JS engine. Superset of #3885, which switches only the worker pool; credit to it for the approach.

Fixes#8356. Fixes#3884 (worker spin reproduced on macOS: four DedicatedWorkers at 100% CPU each, cleared by this change).

Before (main): file renders with no highlighting, worker pool at ~400% CPU; the editable path freezes the tab ("Page Unresponsive"):

before: no highlighting, workers spinning

After: same file, highlighted, UI interactive:

after: highlighted and responsive

Tests

  • pnpm -C apps/web typecheck (tsgo) — clean
  • pnpm -C apps/web test — 281 files, 2,859 tests pass
  • pnpm lint — no errors, none in changed files
  • pnpm -C apps/web build — succeeds; WASM engine emitted as lazy chunk
  • A/B on two dev instances at the same base commit with the [Bug]: File preview panel highlights large files synchronously on the renderer main thread #8356 repro file: pre-fix renders unhighlighted with workers spinning (or freezes the tab via the editable path); with the fix it renders highlighted and stays interactive (screenshots above)

Built with Claude Fable 5 in Claude Code.


Note

Medium Risk
Touches every syntax-highlighting and diff render path; wrong or partial rollout could leave mixed engines on the singleton, but the change is narrowly scoped to preferredHighlighter with no auth or data-handling impact.

Overview
Fixes main-thread freezes and diff-worker CPU spin by standardizing on the Oniguruma WASM highlighter (shiki-wasm) instead of Shiki’s JS regex engine, which could hang indefinitely on some source lines.

Introduces PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts (replacing "shiki-js" in getSharedHighlighter) and threads preferredHighlighter: PREFERRED_HIGHLIGHTER through every Pierre diffs entry point: the diff worker pool, diff panel, file preview (read-only and editable), chat turn/review diffs, PR code tab, and settings font diff preview SSR.

No intended visual change (same themes/grammars); the WASM bundle loads lazily. All sites must agree because the shared highlighter is first-caller-wins.

Reviewed by Cursor Bugbot for commit 6384d96. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Switch syntax highlighter to shiki-wasm to stop UI freezes

  • Adds PREFERRED_HIGHLIGHTER = "shiki-wasm" constant to syntaxHighlighting.ts to switch to the Oniguruma WASM engine.
  • Passes preferredHighlighter: PREFERRED_HIGHLIGHTER into file, diff, and PR code viewer options across the web app.
  • Behavioral Change: All syntax highlighting across the web app switches from the previous default engine to shiki-wasm.

Macroscope summarized 6384d96.

@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20b257ea-6a69-402e-b1cb-f34e41e65fee

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

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 27, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new "every creation site must pass this value" invariant is not applied to the remaining highlighter creation site in apps/web/src/components/settings/SettingsFontPreviews.tsx. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +10 to +18
/**
* Always highlight with the Oniguruma WASM engine. The JavaScript regex engine
* can backtrack catastrophically on ordinary source lines (a Go comment
* containing `{}` and a non-ASCII dash pinned the renderer main thread for 12+
* minutes; the same input tokenizes in under 10ms on WASM). The shared
* highlighter is a first-caller-wins singleton, so every creation site must
* pass this value.
*/
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant documented here ("first-caller-wins singleton, so every creation site must pass this value") is not applied everywhere: apps/web/src/components/settings/SettingsFontPreviews.tsx:80-83 still calls preloadPatchFile({ patch: DIFF_PREVIEW_PATCH, options: { diffStyle: "unified", theme } }), and its own comment above states that pipeline "always awaits the shared highlighter". Appearance settings can easily be opened before any diff/file surface mounts, in which case that call wins the race and pins the JS regex engine for the whole session — exactly the hang this PR fixes.

Suggested smallest fix: add preferredHighlighter: PREFERRED_HIGHLIGHTER to the options passed to preloadPatchFile. If the SSR options type does not accept it, please note in this comment that the SSR path builds its own highlighter rather than the shared singleton, so the invariant stated here is accurate.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 5e5651cpreloadPatchFile reaches DiffHunksRenderer.initializeHighlighter(), which does create the shared singleton with the shiki-js default, so this was a real missed site. preferredHighlighter: PREFERRED_HIGHLIGHTER is now passed there too (the SSR options type accepts it). Typecheck and the 2,859 apps/web unit tests pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

macroscopeapp[bot]
macroscopeappBot previously approved these changes Aug 27, 2026
@macroscopeapp

macroscopeappBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The patch changes the highlighter implementation for every existing file, diff, preview, and worker path, adding a lazy WASM runtime dependency and altering application-wide tokenization behavior. The code is small and consistently wired, but the production blast radius warrants human review.

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

@macroscopeapp
macroscopeappBot dismissed their stale reviewAugust 27, 2026 06:44

Dismissing prior approval to re-evaluate 5e5651c

LetZicoand others added 2 commits September 1, 2026 16:07
…engine
The JavaScript regex engine backtracks catastrophically on ordinary source
lines. A 66-character Go comment line (containing braces and a non-ASCII
dash) pinned the renderer main thread for 12+ minutes when the file preview
panel restored a 476 KB / 10,915-line file: the editor's synchronous
EditorTokenizer #buildStateStack loop calls tokenizeLine per line, and its
500 ms per-line limit cannot fire because vscode-textmate only checks the
clock between regex matches — a single catastrophic match is uninterruptible.
The whole window froze until force-quit (pingdotgg#8356).
The same content tokenizes in 377 ms end-to-end on the Oniguruma WASM engine
(worst single line 9.1 ms, measured via @pierre/diffs getSharedHighlighter on
both engines). The shared highlighter is a first-caller-wins singleton, so
every creation site must name the engine: the app's getSharedHighlighter call,
the diff worker pool options, and each component options object whose
FileRenderer can lazily initialize the singleton with the library's shiki-js
default. PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts names the decision
once.
The workers in DiffWorkerPoolProvider previously ran shiki-js as well, which
plausibly explains the runnable-forever DedicatedWorkers in pingdotgg#3884.
Verified: tsgo --noEmit clean; 2859 apps/web unit tests pass; production
build emits the WASM engine as a lazy ~600 KB chunk; in the dev app the full
repro file tokenizes in 385 ms in-browser where the JS engine previously hung
indefinitely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e comment
Macroscope review: SettingsFontPreviews' preloadPatchFile call reaches
DiffHunksRenderer.initializeHighlighter, which creates the shared
first-caller-wins singleton with the library's shiki-js default. Opening
Appearance settings before any diff/file surface would pin the JS regex
engine for the whole session. Pass PREFERRED_HIGHLIGHTER there too, and
trim the incident detail out of the constant's doc comment.
@t3dotgg
t3dotggforce-pushed the fix/highlighter-wasm-engine branch from 5e5651c to 6384d96CompareSeptember 1, 2026 23:09
@t3dotgg
t3dotgg merged commit feb3ea7 into pingdotgg:mainSep 2, 2026
25 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants

@LetZico@jetersen@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine - #8360

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine
Sep 2, 2026
Merged

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine#8360
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine

Conversation

@LetZico

@LetZicoLetZico commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Opening a large file in the file preview panel could freeze the whole window until force-quit: Shiki's JavaScript regex engine backtracks catastrophically on some ordinary source lines (repro in #8356 is a 66-char Go comment), and one catastrophic match is uninterruptible — the editor's synchronous tokenizer then pins the renderer main thread indefinitely (12+ min at ~200% CPU observed). In the read-only path the same input instead leaves the diff workers spinning forever and highlighting never renders — the symptom in #3884.

Fix: select shiki-wasm (Oniguruma) at every site that can create a highlighter — the shared singleton (syntaxHighlighting.ts, now a PREFERRED_HIGHLIGHTER constant), the diff worker pool, and each component options object. All must agree because the singleton is first-caller-wins and FileRenderer.initializeHighlighter() falls back to the library's shiki-js default. Same grammars and themes, so no visual change; the WASM engine loads as a lazy ~600 KB chunk (CSP already allows 'wasm-unsafe-eval'). The repro file (476 KB, 10,915-line Go) tokenizes in 377 ms on WASM vs a >120 s hang on the JS engine. Superset of #3885, which switches only the worker pool; credit to it for the approach.

Fixes#8356. Fixes#3884 (worker spin reproduced on macOS: four DedicatedWorkers at 100% CPU each, cleared by this change).

Before (main): file renders with no highlighting, worker pool at ~400% CPU; the editable path freezes the tab ("Page Unresponsive"):

before: no highlighting, workers spinning

After: same file, highlighted, UI interactive:

after: highlighted and responsive

Tests

  • pnpm -C apps/web typecheck (tsgo) — clean
  • pnpm -C apps/web test — 281 files, 2,859 tests pass
  • pnpm lint — no errors, none in changed files
  • pnpm -C apps/web build — succeeds; WASM engine emitted as lazy chunk
  • A/B on two dev instances at the same base commit with the [Bug]: File preview panel highlights large files synchronously on the renderer main thread #8356 repro file: pre-fix renders unhighlighted with workers spinning (or freezes the tab via the editable path); with the fix it renders highlighted and stays interactive (screenshots above)

Built with Claude Fable 5 in Claude Code.


Note

Medium Risk
Touches every syntax-highlighting and diff render path; wrong or partial rollout could leave mixed engines on the singleton, but the change is narrowly scoped to preferredHighlighter with no auth or data-handling impact.

Overview
Fixes main-thread freezes and diff-worker CPU spin by standardizing on the Oniguruma WASM highlighter (shiki-wasm) instead of Shiki’s JS regex engine, which could hang indefinitely on some source lines.

Introduces PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts (replacing "shiki-js" in getSharedHighlighter) and threads preferredHighlighter: PREFERRED_HIGHLIGHTER through every Pierre diffs entry point: the diff worker pool, diff panel, file preview (read-only and editable), chat turn/review diffs, PR code tab, and settings font diff preview SSR.

No intended visual change (same themes/grammars); the WASM bundle loads lazily. All sites must agree because the shared highlighter is first-caller-wins.

Reviewed by Cursor Bugbot for commit 6384d96. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Switch syntax highlighter to shiki-wasm to stop UI freezes

  • Adds PREFERRED_HIGHLIGHTER = "shiki-wasm" constant to syntaxHighlighting.ts to switch to the Oniguruma WASM engine.
  • Passes preferredHighlighter: PREFERRED_HIGHLIGHTER into file, diff, and PR code viewer options across the web app.
  • Behavioral Change: All syntax highlighting across the web app switches from the previous default engine to shiki-wasm.

Macroscope summarized 6384d96.

@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20b257ea-6a69-402e-b1cb-f34e41e65fee

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

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 27, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new "every creation site must pass this value" invariant is not applied to the remaining highlighter creation site in apps/web/src/components/settings/SettingsFontPreviews.tsx. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +10 to +18
/**
* Always highlight with the Oniguruma WASM engine. The JavaScript regex engine
* can backtrack catastrophically on ordinary source lines (a Go comment
* containing `{}` and a non-ASCII dash pinned the renderer main thread for 12+
* minutes; the same input tokenizes in under 10ms on WASM). The shared
* highlighter is a first-caller-wins singleton, so every creation site must
* pass this value.
*/
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant documented here ("first-caller-wins singleton, so every creation site must pass this value") is not applied everywhere: apps/web/src/components/settings/SettingsFontPreviews.tsx:80-83 still calls preloadPatchFile({ patch: DIFF_PREVIEW_PATCH, options: { diffStyle: "unified", theme } }), and its own comment above states that pipeline "always awaits the shared highlighter". Appearance settings can easily be opened before any diff/file surface mounts, in which case that call wins the race and pins the JS regex engine for the whole session — exactly the hang this PR fixes.

Suggested smallest fix: add preferredHighlighter: PREFERRED_HIGHLIGHTER to the options passed to preloadPatchFile. If the SSR options type does not accept it, please note in this comment that the SSR path builds its own highlighter rather than the shared singleton, so the invariant stated here is accurate.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 5e5651cpreloadPatchFile reaches DiffHunksRenderer.initializeHighlighter(), which does create the shared singleton with the shiki-js default, so this was a real missed site. preferredHighlighter: PREFERRED_HIGHLIGHTER is now passed there too (the SSR options type accepts it). Typecheck and the 2,859 apps/web unit tests pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

macroscopeapp[bot]
macroscopeappBot previously approved these changes Aug 27, 2026
@macroscopeapp

macroscopeappBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The patch changes the highlighter implementation for every existing file, diff, preview, and worker path, adding a lazy WASM runtime dependency and altering application-wide tokenization behavior. The code is small and consistently wired, but the production blast radius warrants human review.

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

@macroscopeapp
macroscopeappBot dismissed their stale reviewAugust 27, 2026 06:44

Dismissing prior approval to re-evaluate 5e5651c

LetZicoand others added 2 commits September 1, 2026 16:07
…engine
The JavaScript regex engine backtracks catastrophically on ordinary source
lines. A 66-character Go comment line (containing braces and a non-ASCII
dash) pinned the renderer main thread for 12+ minutes when the file preview
panel restored a 476 KB / 10,915-line file: the editor's synchronous
EditorTokenizer #buildStateStack loop calls tokenizeLine per line, and its
500 ms per-line limit cannot fire because vscode-textmate only checks the
clock between regex matches — a single catastrophic match is uninterruptible.
The whole window froze until force-quit (pingdotgg#8356).
The same content tokenizes in 377 ms end-to-end on the Oniguruma WASM engine
(worst single line 9.1 ms, measured via @pierre/diffs getSharedHighlighter on
both engines). The shared highlighter is a first-caller-wins singleton, so
every creation site must name the engine: the app's getSharedHighlighter call,
the diff worker pool options, and each component options object whose
FileRenderer can lazily initialize the singleton with the library's shiki-js
default. PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts names the decision
once.
The workers in DiffWorkerPoolProvider previously ran shiki-js as well, which
plausibly explains the runnable-forever DedicatedWorkers in pingdotgg#3884.
Verified: tsgo --noEmit clean; 2859 apps/web unit tests pass; production
build emits the WASM engine as a lazy ~600 KB chunk; in the dev app the full
repro file tokenizes in 385 ms in-browser where the JS engine previously hung
indefinitely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e comment
Macroscope review: SettingsFontPreviews' preloadPatchFile call reaches
DiffHunksRenderer.initializeHighlighter, which creates the shared
first-caller-wins singleton with the library's shiki-js default. Opening
Appearance settings before any diff/file surface would pin the JS regex
engine for the whole session. Pass PREFERRED_HIGHLIGHTER there too, and
trim the incident detail out of the constant's doc comment.
@t3dotgg
t3dotggforce-pushed the fix/highlighter-wasm-engine branch from 5e5651c to 6384d96CompareSeptember 1, 2026 23:09
@t3dotgg
t3dotgg merged commit feb3ea7 into pingdotgg:mainSep 2, 2026
25 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants

@LetZico@jetersen@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine - #8360

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine
Sep 2, 2026
Merged

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine#8360
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine

Conversation

@LetZico

@LetZicoLetZico commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Opening a large file in the file preview panel could freeze the whole window until force-quit: Shiki's JavaScript regex engine backtracks catastrophically on some ordinary source lines (repro in #8356 is a 66-char Go comment), and one catastrophic match is uninterruptible — the editor's synchronous tokenizer then pins the renderer main thread indefinitely (12+ min at ~200% CPU observed). In the read-only path the same input instead leaves the diff workers spinning forever and highlighting never renders — the symptom in #3884.

Fix: select shiki-wasm (Oniguruma) at every site that can create a highlighter — the shared singleton (syntaxHighlighting.ts, now a PREFERRED_HIGHLIGHTER constant), the diff worker pool, and each component options object. All must agree because the singleton is first-caller-wins and FileRenderer.initializeHighlighter() falls back to the library's shiki-js default. Same grammars and themes, so no visual change; the WASM engine loads as a lazy ~600 KB chunk (CSP already allows 'wasm-unsafe-eval'). The repro file (476 KB, 10,915-line Go) tokenizes in 377 ms on WASM vs a >120 s hang on the JS engine. Superset of #3885, which switches only the worker pool; credit to it for the approach.

Fixes#8356. Fixes#3884 (worker spin reproduced on macOS: four DedicatedWorkers at 100% CPU each, cleared by this change).

Before (main): file renders with no highlighting, worker pool at ~400% CPU; the editable path freezes the tab ("Page Unresponsive"):

before: no highlighting, workers spinning

After: same file, highlighted, UI interactive:

after: highlighted and responsive

Tests

  • pnpm -C apps/web typecheck (tsgo) — clean
  • pnpm -C apps/web test — 281 files, 2,859 tests pass
  • pnpm lint — no errors, none in changed files
  • pnpm -C apps/web build — succeeds; WASM engine emitted as lazy chunk
  • A/B on two dev instances at the same base commit with the [Bug]: File preview panel highlights large files synchronously on the renderer main thread #8356 repro file: pre-fix renders unhighlighted with workers spinning (or freezes the tab via the editable path); with the fix it renders highlighted and stays interactive (screenshots above)

Built with Claude Fable 5 in Claude Code.


Note

Medium Risk
Touches every syntax-highlighting and diff render path; wrong or partial rollout could leave mixed engines on the singleton, but the change is narrowly scoped to preferredHighlighter with no auth or data-handling impact.

Overview
Fixes main-thread freezes and diff-worker CPU spin by standardizing on the Oniguruma WASM highlighter (shiki-wasm) instead of Shiki’s JS regex engine, which could hang indefinitely on some source lines.

Introduces PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts (replacing "shiki-js" in getSharedHighlighter) and threads preferredHighlighter: PREFERRED_HIGHLIGHTER through every Pierre diffs entry point: the diff worker pool, diff panel, file preview (read-only and editable), chat turn/review diffs, PR code tab, and settings font diff preview SSR.

No intended visual change (same themes/grammars); the WASM bundle loads lazily. All sites must agree because the shared highlighter is first-caller-wins.

Reviewed by Cursor Bugbot for commit 6384d96. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Switch syntax highlighter to shiki-wasm to stop UI freezes

  • Adds PREFERRED_HIGHLIGHTER = "shiki-wasm" constant to syntaxHighlighting.ts to switch to the Oniguruma WASM engine.
  • Passes preferredHighlighter: PREFERRED_HIGHLIGHTER into file, diff, and PR code viewer options across the web app.
  • Behavioral Change: All syntax highlighting across the web app switches from the previous default engine to shiki-wasm.

Macroscope summarized 6384d96.

@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20b257ea-6a69-402e-b1cb-f34e41e65fee

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

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 27, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new "every creation site must pass this value" invariant is not applied to the remaining highlighter creation site in apps/web/src/components/settings/SettingsFontPreviews.tsx. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +10 to +18
/**
* Always highlight with the Oniguruma WASM engine. The JavaScript regex engine
* can backtrack catastrophically on ordinary source lines (a Go comment
* containing `{}` and a non-ASCII dash pinned the renderer main thread for 12+
* minutes; the same input tokenizes in under 10ms on WASM). The shared
* highlighter is a first-caller-wins singleton, so every creation site must
* pass this value.
*/
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant documented here ("first-caller-wins singleton, so every creation site must pass this value") is not applied everywhere: apps/web/src/components/settings/SettingsFontPreviews.tsx:80-83 still calls preloadPatchFile({ patch: DIFF_PREVIEW_PATCH, options: { diffStyle: "unified", theme } }), and its own comment above states that pipeline "always awaits the shared highlighter". Appearance settings can easily be opened before any diff/file surface mounts, in which case that call wins the race and pins the JS regex engine for the whole session — exactly the hang this PR fixes.

Suggested smallest fix: add preferredHighlighter: PREFERRED_HIGHLIGHTER to the options passed to preloadPatchFile. If the SSR options type does not accept it, please note in this comment that the SSR path builds its own highlighter rather than the shared singleton, so the invariant stated here is accurate.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 5e5651cpreloadPatchFile reaches DiffHunksRenderer.initializeHighlighter(), which does create the shared singleton with the shiki-js default, so this was a real missed site. preferredHighlighter: PREFERRED_HIGHLIGHTER is now passed there too (the SSR options type accepts it). Typecheck and the 2,859 apps/web unit tests pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

macroscopeapp[bot]
macroscopeappBot previously approved these changes Aug 27, 2026
@macroscopeapp

macroscopeappBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The patch changes the highlighter implementation for every existing file, diff, preview, and worker path, adding a lazy WASM runtime dependency and altering application-wide tokenization behavior. The code is small and consistently wired, but the production blast radius warrants human review.

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

@macroscopeapp
macroscopeappBot dismissed their stale reviewAugust 27, 2026 06:44

Dismissing prior approval to re-evaluate 5e5651c

LetZicoand others added 2 commits September 1, 2026 16:07
…engine
The JavaScript regex engine backtracks catastrophically on ordinary source
lines. A 66-character Go comment line (containing braces and a non-ASCII
dash) pinned the renderer main thread for 12+ minutes when the file preview
panel restored a 476 KB / 10,915-line file: the editor's synchronous
EditorTokenizer #buildStateStack loop calls tokenizeLine per line, and its
500 ms per-line limit cannot fire because vscode-textmate only checks the
clock between regex matches — a single catastrophic match is uninterruptible.
The whole window froze until force-quit (pingdotgg#8356).
The same content tokenizes in 377 ms end-to-end on the Oniguruma WASM engine
(worst single line 9.1 ms, measured via @pierre/diffs getSharedHighlighter on
both engines). The shared highlighter is a first-caller-wins singleton, so
every creation site must name the engine: the app's getSharedHighlighter call,
the diff worker pool options, and each component options object whose
FileRenderer can lazily initialize the singleton with the library's shiki-js
default. PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts names the decision
once.
The workers in DiffWorkerPoolProvider previously ran shiki-js as well, which
plausibly explains the runnable-forever DedicatedWorkers in pingdotgg#3884.
Verified: tsgo --noEmit clean; 2859 apps/web unit tests pass; production
build emits the WASM engine as a lazy ~600 KB chunk; in the dev app the full
repro file tokenizes in 385 ms in-browser where the JS engine previously hung
indefinitely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e comment
Macroscope review: SettingsFontPreviews' preloadPatchFile call reaches
DiffHunksRenderer.initializeHighlighter, which creates the shared
first-caller-wins singleton with the library's shiki-js default. Opening
Appearance settings before any diff/file surface would pin the JS regex
engine for the whole session. Pass PREFERRED_HIGHLIGHTER there too, and
trim the incident detail out of the constant's doc comment.
@t3dotgg
t3dotggforce-pushed the fix/highlighter-wasm-engine branch from 5e5651c to 6384d96CompareSeptember 1, 2026 23:09
@t3dotgg
t3dotgg merged commit feb3ea7 into pingdotgg:mainSep 2, 2026
25 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants

@LetZico@jetersen@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine - #8360

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine
Sep 2, 2026
Merged

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine#8360
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine

Conversation

@LetZico

@LetZicoLetZico commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Opening a large file in the file preview panel could freeze the whole window until force-quit: Shiki's JavaScript regex engine backtracks catastrophically on some ordinary source lines (repro in #8356 is a 66-char Go comment), and one catastrophic match is uninterruptible — the editor's synchronous tokenizer then pins the renderer main thread indefinitely (12+ min at ~200% CPU observed). In the read-only path the same input instead leaves the diff workers spinning forever and highlighting never renders — the symptom in #3884.

Fix: select shiki-wasm (Oniguruma) at every site that can create a highlighter — the shared singleton (syntaxHighlighting.ts, now a PREFERRED_HIGHLIGHTER constant), the diff worker pool, and each component options object. All must agree because the singleton is first-caller-wins and FileRenderer.initializeHighlighter() falls back to the library's shiki-js default. Same grammars and themes, so no visual change; the WASM engine loads as a lazy ~600 KB chunk (CSP already allows 'wasm-unsafe-eval'). The repro file (476 KB, 10,915-line Go) tokenizes in 377 ms on WASM vs a >120 s hang on the JS engine. Superset of #3885, which switches only the worker pool; credit to it for the approach.

Fixes#8356. Fixes#3884 (worker spin reproduced on macOS: four DedicatedWorkers at 100% CPU each, cleared by this change).

Before (main): file renders with no highlighting, worker pool at ~400% CPU; the editable path freezes the tab ("Page Unresponsive"):

before: no highlighting, workers spinning

After: same file, highlighted, UI interactive:

after: highlighted and responsive

Tests

  • pnpm -C apps/web typecheck (tsgo) — clean
  • pnpm -C apps/web test — 281 files, 2,859 tests pass
  • pnpm lint — no errors, none in changed files
  • pnpm -C apps/web build — succeeds; WASM engine emitted as lazy chunk
  • A/B on two dev instances at the same base commit with the [Bug]: File preview panel highlights large files synchronously on the renderer main thread #8356 repro file: pre-fix renders unhighlighted with workers spinning (or freezes the tab via the editable path); with the fix it renders highlighted and stays interactive (screenshots above)

Built with Claude Fable 5 in Claude Code.


Note

Medium Risk
Touches every syntax-highlighting and diff render path; wrong or partial rollout could leave mixed engines on the singleton, but the change is narrowly scoped to preferredHighlighter with no auth or data-handling impact.

Overview
Fixes main-thread freezes and diff-worker CPU spin by standardizing on the Oniguruma WASM highlighter (shiki-wasm) instead of Shiki’s JS regex engine, which could hang indefinitely on some source lines.

Introduces PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts (replacing "shiki-js" in getSharedHighlighter) and threads preferredHighlighter: PREFERRED_HIGHLIGHTER through every Pierre diffs entry point: the diff worker pool, diff panel, file preview (read-only and editable), chat turn/review diffs, PR code tab, and settings font diff preview SSR.

No intended visual change (same themes/grammars); the WASM bundle loads lazily. All sites must agree because the shared highlighter is first-caller-wins.

Reviewed by Cursor Bugbot for commit 6384d96. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Switch syntax highlighter to shiki-wasm to stop UI freezes

  • Adds PREFERRED_HIGHLIGHTER = "shiki-wasm" constant to syntaxHighlighting.ts to switch to the Oniguruma WASM engine.
  • Passes preferredHighlighter: PREFERRED_HIGHLIGHTER into file, diff, and PR code viewer options across the web app.
  • Behavioral Change: All syntax highlighting across the web app switches from the previous default engine to shiki-wasm.

Macroscope summarized 6384d96.

@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20b257ea-6a69-402e-b1cb-f34e41e65fee

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

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 27, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new "every creation site must pass this value" invariant is not applied to the remaining highlighter creation site in apps/web/src/components/settings/SettingsFontPreviews.tsx. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +10 to +18
/**
* Always highlight with the Oniguruma WASM engine. The JavaScript regex engine
* can backtrack catastrophically on ordinary source lines (a Go comment
* containing `{}` and a non-ASCII dash pinned the renderer main thread for 12+
* minutes; the same input tokenizes in under 10ms on WASM). The shared
* highlighter is a first-caller-wins singleton, so every creation site must
* pass this value.
*/
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant documented here ("first-caller-wins singleton, so every creation site must pass this value") is not applied everywhere: apps/web/src/components/settings/SettingsFontPreviews.tsx:80-83 still calls preloadPatchFile({ patch: DIFF_PREVIEW_PATCH, options: { diffStyle: "unified", theme } }), and its own comment above states that pipeline "always awaits the shared highlighter". Appearance settings can easily be opened before any diff/file surface mounts, in which case that call wins the race and pins the JS regex engine for the whole session — exactly the hang this PR fixes.

Suggested smallest fix: add preferredHighlighter: PREFERRED_HIGHLIGHTER to the options passed to preloadPatchFile. If the SSR options type does not accept it, please note in this comment that the SSR path builds its own highlighter rather than the shared singleton, so the invariant stated here is accurate.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 5e5651cpreloadPatchFile reaches DiffHunksRenderer.initializeHighlighter(), which does create the shared singleton with the shiki-js default, so this was a real missed site. preferredHighlighter: PREFERRED_HIGHLIGHTER is now passed there too (the SSR options type accepts it). Typecheck and the 2,859 apps/web unit tests pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

macroscopeapp[bot]
macroscopeappBot previously approved these changes Aug 27, 2026
@macroscopeapp

macroscopeappBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The patch changes the highlighter implementation for every existing file, diff, preview, and worker path, adding a lazy WASM runtime dependency and altering application-wide tokenization behavior. The code is small and consistently wired, but the production blast radius warrants human review.

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

@macroscopeapp
macroscopeappBot dismissed their stale reviewAugust 27, 2026 06:44

Dismissing prior approval to re-evaluate 5e5651c

LetZicoand others added 2 commits September 1, 2026 16:07
…engine
The JavaScript regex engine backtracks catastrophically on ordinary source
lines. A 66-character Go comment line (containing braces and a non-ASCII
dash) pinned the renderer main thread for 12+ minutes when the file preview
panel restored a 476 KB / 10,915-line file: the editor's synchronous
EditorTokenizer #buildStateStack loop calls tokenizeLine per line, and its
500 ms per-line limit cannot fire because vscode-textmate only checks the
clock between regex matches — a single catastrophic match is uninterruptible.
The whole window froze until force-quit (pingdotgg#8356).
The same content tokenizes in 377 ms end-to-end on the Oniguruma WASM engine
(worst single line 9.1 ms, measured via @pierre/diffs getSharedHighlighter on
both engines). The shared highlighter is a first-caller-wins singleton, so
every creation site must name the engine: the app's getSharedHighlighter call,
the diff worker pool options, and each component options object whose
FileRenderer can lazily initialize the singleton with the library's shiki-js
default. PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts names the decision
once.
The workers in DiffWorkerPoolProvider previously ran shiki-js as well, which
plausibly explains the runnable-forever DedicatedWorkers in pingdotgg#3884.
Verified: tsgo --noEmit clean; 2859 apps/web unit tests pass; production
build emits the WASM engine as a lazy ~600 KB chunk; in the dev app the full
repro file tokenizes in 385 ms in-browser where the JS engine previously hung
indefinitely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e comment
Macroscope review: SettingsFontPreviews' preloadPatchFile call reaches
DiffHunksRenderer.initializeHighlighter, which creates the shared
first-caller-wins singleton with the library's shiki-js default. Opening
Appearance settings before any diff/file surface would pin the JS regex
engine for the whole session. Pass PREFERRED_HIGHLIGHTER there too, and
trim the incident detail out of the constant's doc comment.
@t3dotgg
t3dotggforce-pushed the fix/highlighter-wasm-engine branch from 5e5651c to 6384d96CompareSeptember 1, 2026 23:09
@t3dotgg
t3dotgg merged commit feb3ea7 into pingdotgg:mainSep 2, 2026
25 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants

@LetZico@jetersen@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine - #8360

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine
Sep 2, 2026
Merged

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine#8360
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine

Conversation

@LetZico

@LetZicoLetZico commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Opening a large file in the file preview panel could freeze the whole window until force-quit: Shiki's JavaScript regex engine backtracks catastrophically on some ordinary source lines (repro in #8356 is a 66-char Go comment), and one catastrophic match is uninterruptible — the editor's synchronous tokenizer then pins the renderer main thread indefinitely (12+ min at ~200% CPU observed). In the read-only path the same input instead leaves the diff workers spinning forever and highlighting never renders — the symptom in #3884.

Fix: select shiki-wasm (Oniguruma) at every site that can create a highlighter — the shared singleton (syntaxHighlighting.ts, now a PREFERRED_HIGHLIGHTER constant), the diff worker pool, and each component options object. All must agree because the singleton is first-caller-wins and FileRenderer.initializeHighlighter() falls back to the library's shiki-js default. Same grammars and themes, so no visual change; the WASM engine loads as a lazy ~600 KB chunk (CSP already allows 'wasm-unsafe-eval'). The repro file (476 KB, 10,915-line Go) tokenizes in 377 ms on WASM vs a >120 s hang on the JS engine. Superset of #3885, which switches only the worker pool; credit to it for the approach.

Fixes#8356. Fixes#3884 (worker spin reproduced on macOS: four DedicatedWorkers at 100% CPU each, cleared by this change).

Before (main): file renders with no highlighting, worker pool at ~400% CPU; the editable path freezes the tab ("Page Unresponsive"):

before: no highlighting, workers spinning

After: same file, highlighted, UI interactive:

after: highlighted and responsive

Tests

  • pnpm -C apps/web typecheck (tsgo) — clean
  • pnpm -C apps/web test — 281 files, 2,859 tests pass
  • pnpm lint — no errors, none in changed files
  • pnpm -C apps/web build — succeeds; WASM engine emitted as lazy chunk
  • A/B on two dev instances at the same base commit with the [Bug]: File preview panel highlights large files synchronously on the renderer main thread #8356 repro file: pre-fix renders unhighlighted with workers spinning (or freezes the tab via the editable path); with the fix it renders highlighted and stays interactive (screenshots above)

Built with Claude Fable 5 in Claude Code.


Note

Medium Risk
Touches every syntax-highlighting and diff render path; wrong or partial rollout could leave mixed engines on the singleton, but the change is narrowly scoped to preferredHighlighter with no auth or data-handling impact.

Overview
Fixes main-thread freezes and diff-worker CPU spin by standardizing on the Oniguruma WASM highlighter (shiki-wasm) instead of Shiki’s JS regex engine, which could hang indefinitely on some source lines.

Introduces PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts (replacing "shiki-js" in getSharedHighlighter) and threads preferredHighlighter: PREFERRED_HIGHLIGHTER through every Pierre diffs entry point: the diff worker pool, diff panel, file preview (read-only and editable), chat turn/review diffs, PR code tab, and settings font diff preview SSR.

No intended visual change (same themes/grammars); the WASM bundle loads lazily. All sites must agree because the shared highlighter is first-caller-wins.

Reviewed by Cursor Bugbot for commit 6384d96. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Switch syntax highlighter to shiki-wasm to stop UI freezes

  • Adds PREFERRED_HIGHLIGHTER = "shiki-wasm" constant to syntaxHighlighting.ts to switch to the Oniguruma WASM engine.
  • Passes preferredHighlighter: PREFERRED_HIGHLIGHTER into file, diff, and PR code viewer options across the web app.
  • Behavioral Change: All syntax highlighting across the web app switches from the previous default engine to shiki-wasm.

Macroscope summarized 6384d96.

@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20b257ea-6a69-402e-b1cb-f34e41e65fee

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

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 27, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new "every creation site must pass this value" invariant is not applied to the remaining highlighter creation site in apps/web/src/components/settings/SettingsFontPreviews.tsx. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +10 to +18
/**
* Always highlight with the Oniguruma WASM engine. The JavaScript regex engine
* can backtrack catastrophically on ordinary source lines (a Go comment
* containing `{}` and a non-ASCII dash pinned the renderer main thread for 12+
* minutes; the same input tokenizes in under 10ms on WASM). The shared
* highlighter is a first-caller-wins singleton, so every creation site must
* pass this value.
*/
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant documented here ("first-caller-wins singleton, so every creation site must pass this value") is not applied everywhere: apps/web/src/components/settings/SettingsFontPreviews.tsx:80-83 still calls preloadPatchFile({ patch: DIFF_PREVIEW_PATCH, options: { diffStyle: "unified", theme } }), and its own comment above states that pipeline "always awaits the shared highlighter". Appearance settings can easily be opened before any diff/file surface mounts, in which case that call wins the race and pins the JS regex engine for the whole session — exactly the hang this PR fixes.

Suggested smallest fix: add preferredHighlighter: PREFERRED_HIGHLIGHTER to the options passed to preloadPatchFile. If the SSR options type does not accept it, please note in this comment that the SSR path builds its own highlighter rather than the shared singleton, so the invariant stated here is accurate.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 5e5651cpreloadPatchFile reaches DiffHunksRenderer.initializeHighlighter(), which does create the shared singleton with the shiki-js default, so this was a real missed site. preferredHighlighter: PREFERRED_HIGHLIGHTER is now passed there too (the SSR options type accepts it). Typecheck and the 2,859 apps/web unit tests pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

macroscopeapp[bot]
macroscopeappBot previously approved these changes Aug 27, 2026
@macroscopeapp

macroscopeappBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The patch changes the highlighter implementation for every existing file, diff, preview, and worker path, adding a lazy WASM runtime dependency and altering application-wide tokenization behavior. The code is small and consistently wired, but the production blast radius warrants human review.

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

@macroscopeapp
macroscopeappBot dismissed their stale reviewAugust 27, 2026 06:44

Dismissing prior approval to re-evaluate 5e5651c

LetZicoand others added 2 commits September 1, 2026 16:07
…engine
The JavaScript regex engine backtracks catastrophically on ordinary source
lines. A 66-character Go comment line (containing braces and a non-ASCII
dash) pinned the renderer main thread for 12+ minutes when the file preview
panel restored a 476 KB / 10,915-line file: the editor's synchronous
EditorTokenizer #buildStateStack loop calls tokenizeLine per line, and its
500 ms per-line limit cannot fire because vscode-textmate only checks the
clock between regex matches — a single catastrophic match is uninterruptible.
The whole window froze until force-quit (pingdotgg#8356).
The same content tokenizes in 377 ms end-to-end on the Oniguruma WASM engine
(worst single line 9.1 ms, measured via @pierre/diffs getSharedHighlighter on
both engines). The shared highlighter is a first-caller-wins singleton, so
every creation site must name the engine: the app's getSharedHighlighter call,
the diff worker pool options, and each component options object whose
FileRenderer can lazily initialize the singleton with the library's shiki-js
default. PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts names the decision
once.
The workers in DiffWorkerPoolProvider previously ran shiki-js as well, which
plausibly explains the runnable-forever DedicatedWorkers in pingdotgg#3884.
Verified: tsgo --noEmit clean; 2859 apps/web unit tests pass; production
build emits the WASM engine as a lazy ~600 KB chunk; in the dev app the full
repro file tokenizes in 385 ms in-browser where the JS engine previously hung
indefinitely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e comment
Macroscope review: SettingsFontPreviews' preloadPatchFile call reaches
DiffHunksRenderer.initializeHighlighter, which creates the shared
first-caller-wins singleton with the library's shiki-js default. Opening
Appearance settings before any diff/file surface would pin the JS regex
engine for the whole session. Pass PREFERRED_HIGHLIGHTER there too, and
trim the incident detail out of the constant's doc comment.
@t3dotgg
t3dotggforce-pushed the fix/highlighter-wasm-engine branch from 5e5651c to 6384d96CompareSeptember 1, 2026 23:09
@t3dotgg
t3dotgg merged commit feb3ea7 into pingdotgg:mainSep 2, 2026
25 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants

@LetZico@jetersen@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine - #8360

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine
Sep 2, 2026
Merged

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine#8360
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine

Conversation

@LetZico

@LetZicoLetZico commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Opening a large file in the file preview panel could freeze the whole window until force-quit: Shiki's JavaScript regex engine backtracks catastrophically on some ordinary source lines (repro in #8356 is a 66-char Go comment), and one catastrophic match is uninterruptible — the editor's synchronous tokenizer then pins the renderer main thread indefinitely (12+ min at ~200% CPU observed). In the read-only path the same input instead leaves the diff workers spinning forever and highlighting never renders — the symptom in #3884.

Fix: select shiki-wasm (Oniguruma) at every site that can create a highlighter — the shared singleton (syntaxHighlighting.ts, now a PREFERRED_HIGHLIGHTER constant), the diff worker pool, and each component options object. All must agree because the singleton is first-caller-wins and FileRenderer.initializeHighlighter() falls back to the library's shiki-js default. Same grammars and themes, so no visual change; the WASM engine loads as a lazy ~600 KB chunk (CSP already allows 'wasm-unsafe-eval'). The repro file (476 KB, 10,915-line Go) tokenizes in 377 ms on WASM vs a >120 s hang on the JS engine. Superset of #3885, which switches only the worker pool; credit to it for the approach.

Fixes#8356. Fixes#3884 (worker spin reproduced on macOS: four DedicatedWorkers at 100% CPU each, cleared by this change).

Before (main): file renders with no highlighting, worker pool at ~400% CPU; the editable path freezes the tab ("Page Unresponsive"):

before: no highlighting, workers spinning

After: same file, highlighted, UI interactive:

after: highlighted and responsive

Tests

  • pnpm -C apps/web typecheck (tsgo) — clean
  • pnpm -C apps/web test — 281 files, 2,859 tests pass
  • pnpm lint — no errors, none in changed files
  • pnpm -C apps/web build — succeeds; WASM engine emitted as lazy chunk
  • A/B on two dev instances at the same base commit with the [Bug]: File preview panel highlights large files synchronously on the renderer main thread #8356 repro file: pre-fix renders unhighlighted with workers spinning (or freezes the tab via the editable path); with the fix it renders highlighted and stays interactive (screenshots above)

Built with Claude Fable 5 in Claude Code.


Note

Medium Risk
Touches every syntax-highlighting and diff render path; wrong or partial rollout could leave mixed engines on the singleton, but the change is narrowly scoped to preferredHighlighter with no auth or data-handling impact.

Overview
Fixes main-thread freezes and diff-worker CPU spin by standardizing on the Oniguruma WASM highlighter (shiki-wasm) instead of Shiki’s JS regex engine, which could hang indefinitely on some source lines.

Introduces PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts (replacing "shiki-js" in getSharedHighlighter) and threads preferredHighlighter: PREFERRED_HIGHLIGHTER through every Pierre diffs entry point: the diff worker pool, diff panel, file preview (read-only and editable), chat turn/review diffs, PR code tab, and settings font diff preview SSR.

No intended visual change (same themes/grammars); the WASM bundle loads lazily. All sites must agree because the shared highlighter is first-caller-wins.

Reviewed by Cursor Bugbot for commit 6384d96. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Switch syntax highlighter to shiki-wasm to stop UI freezes

  • Adds PREFERRED_HIGHLIGHTER = "shiki-wasm" constant to syntaxHighlighting.ts to switch to the Oniguruma WASM engine.
  • Passes preferredHighlighter: PREFERRED_HIGHLIGHTER into file, diff, and PR code viewer options across the web app.
  • Behavioral Change: All syntax highlighting across the web app switches from the previous default engine to shiki-wasm.

Macroscope summarized 6384d96.

@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20b257ea-6a69-402e-b1cb-f34e41e65fee

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

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 27, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new "every creation site must pass this value" invariant is not applied to the remaining highlighter creation site in apps/web/src/components/settings/SettingsFontPreviews.tsx. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +10 to +18
/**
* Always highlight with the Oniguruma WASM engine. The JavaScript regex engine
* can backtrack catastrophically on ordinary source lines (a Go comment
* containing `{}` and a non-ASCII dash pinned the renderer main thread for 12+
* minutes; the same input tokenizes in under 10ms on WASM). The shared
* highlighter is a first-caller-wins singleton, so every creation site must
* pass this value.
*/
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant documented here ("first-caller-wins singleton, so every creation site must pass this value") is not applied everywhere: apps/web/src/components/settings/SettingsFontPreviews.tsx:80-83 still calls preloadPatchFile({ patch: DIFF_PREVIEW_PATCH, options: { diffStyle: "unified", theme } }), and its own comment above states that pipeline "always awaits the shared highlighter". Appearance settings can easily be opened before any diff/file surface mounts, in which case that call wins the race and pins the JS regex engine for the whole session — exactly the hang this PR fixes.

Suggested smallest fix: add preferredHighlighter: PREFERRED_HIGHLIGHTER to the options passed to preloadPatchFile. If the SSR options type does not accept it, please note in this comment that the SSR path builds its own highlighter rather than the shared singleton, so the invariant stated here is accurate.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 5e5651cpreloadPatchFile reaches DiffHunksRenderer.initializeHighlighter(), which does create the shared singleton with the shiki-js default, so this was a real missed site. preferredHighlighter: PREFERRED_HIGHLIGHTER is now passed there too (the SSR options type accepts it). Typecheck and the 2,859 apps/web unit tests pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

macroscopeapp[bot]
macroscopeappBot previously approved these changes Aug 27, 2026
@macroscopeapp

macroscopeappBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The patch changes the highlighter implementation for every existing file, diff, preview, and worker path, adding a lazy WASM runtime dependency and altering application-wide tokenization behavior. The code is small and consistently wired, but the production blast radius warrants human review.

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

@macroscopeapp
macroscopeappBot dismissed their stale reviewAugust 27, 2026 06:44

Dismissing prior approval to re-evaluate 5e5651c

LetZicoand others added 2 commits September 1, 2026 16:07
…engine
The JavaScript regex engine backtracks catastrophically on ordinary source
lines. A 66-character Go comment line (containing braces and a non-ASCII
dash) pinned the renderer main thread for 12+ minutes when the file preview
panel restored a 476 KB / 10,915-line file: the editor's synchronous
EditorTokenizer #buildStateStack loop calls tokenizeLine per line, and its
500 ms per-line limit cannot fire because vscode-textmate only checks the
clock between regex matches — a single catastrophic match is uninterruptible.
The whole window froze until force-quit (pingdotgg#8356).
The same content tokenizes in 377 ms end-to-end on the Oniguruma WASM engine
(worst single line 9.1 ms, measured via @pierre/diffs getSharedHighlighter on
both engines). The shared highlighter is a first-caller-wins singleton, so
every creation site must name the engine: the app's getSharedHighlighter call,
the diff worker pool options, and each component options object whose
FileRenderer can lazily initialize the singleton with the library's shiki-js
default. PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts names the decision
once.
The workers in DiffWorkerPoolProvider previously ran shiki-js as well, which
plausibly explains the runnable-forever DedicatedWorkers in pingdotgg#3884.
Verified: tsgo --noEmit clean; 2859 apps/web unit tests pass; production
build emits the WASM engine as a lazy ~600 KB chunk; in the dev app the full
repro file tokenizes in 385 ms in-browser where the JS engine previously hung
indefinitely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e comment
Macroscope review: SettingsFontPreviews' preloadPatchFile call reaches
DiffHunksRenderer.initializeHighlighter, which creates the shared
first-caller-wins singleton with the library's shiki-js default. Opening
Appearance settings before any diff/file surface would pin the JS regex
engine for the whole session. Pass PREFERRED_HIGHLIGHTER there too, and
trim the incident detail out of the constant's doc comment.
@t3dotgg
t3dotggforce-pushed the fix/highlighter-wasm-engine branch from 5e5651c to 6384d96CompareSeptember 1, 2026 23:09
@t3dotgg
t3dotgg merged commit feb3ea7 into pingdotgg:mainSep 2, 2026
25 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants

@LetZico@jetersen@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine - #8360

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine
Sep 2, 2026
Merged

fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine#8360
t3dotgg merged 2 commits into
pingdotgg:mainfrom
LetZico:fix/highlighter-wasm-engine

Conversation

@LetZico

@LetZicoLetZico commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Opening a large file in the file preview panel could freeze the whole window until force-quit: Shiki's JavaScript regex engine backtracks catastrophically on some ordinary source lines (repro in #8356 is a 66-char Go comment), and one catastrophic match is uninterruptible — the editor's synchronous tokenizer then pins the renderer main thread indefinitely (12+ min at ~200% CPU observed). In the read-only path the same input instead leaves the diff workers spinning forever and highlighting never renders — the symptom in #3884.

Fix: select shiki-wasm (Oniguruma) at every site that can create a highlighter — the shared singleton (syntaxHighlighting.ts, now a PREFERRED_HIGHLIGHTER constant), the diff worker pool, and each component options object. All must agree because the singleton is first-caller-wins and FileRenderer.initializeHighlighter() falls back to the library's shiki-js default. Same grammars and themes, so no visual change; the WASM engine loads as a lazy ~600 KB chunk (CSP already allows 'wasm-unsafe-eval'). The repro file (476 KB, 10,915-line Go) tokenizes in 377 ms on WASM vs a >120 s hang on the JS engine. Superset of #3885, which switches only the worker pool; credit to it for the approach.

Fixes#8356. Fixes#3884 (worker spin reproduced on macOS: four DedicatedWorkers at 100% CPU each, cleared by this change).

Before (main): file renders with no highlighting, worker pool at ~400% CPU; the editable path freezes the tab ("Page Unresponsive"):

before: no highlighting, workers spinning

After: same file, highlighted, UI interactive:

after: highlighted and responsive

Tests

  • pnpm -C apps/web typecheck (tsgo) — clean
  • pnpm -C apps/web test — 281 files, 2,859 tests pass
  • pnpm lint — no errors, none in changed files
  • pnpm -C apps/web build — succeeds; WASM engine emitted as lazy chunk
  • A/B on two dev instances at the same base commit with the [Bug]: File preview panel highlights large files synchronously on the renderer main thread #8356 repro file: pre-fix renders unhighlighted with workers spinning (or freezes the tab via the editable path); with the fix it renders highlighted and stays interactive (screenshots above)

Built with Claude Fable 5 in Claude Code.


Note

Medium Risk
Touches every syntax-highlighting and diff render path; wrong or partial rollout could leave mixed engines on the singleton, but the change is narrowly scoped to preferredHighlighter with no auth or data-handling impact.

Overview
Fixes main-thread freezes and diff-worker CPU spin by standardizing on the Oniguruma WASM highlighter (shiki-wasm) instead of Shiki’s JS regex engine, which could hang indefinitely on some source lines.

Introduces PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts (replacing "shiki-js" in getSharedHighlighter) and threads preferredHighlighter: PREFERRED_HIGHLIGHTER through every Pierre diffs entry point: the diff worker pool, diff panel, file preview (read-only and editable), chat turn/review diffs, PR code tab, and settings font diff preview SSR.

No intended visual change (same themes/grammars); the WASM bundle loads lazily. All sites must agree because the shared highlighter is first-caller-wins.

Reviewed by Cursor Bugbot for commit 6384d96. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Switch syntax highlighter to shiki-wasm to stop UI freezes

  • Adds PREFERRED_HIGHLIGHTER = "shiki-wasm" constant to syntaxHighlighting.ts to switch to the Oniguruma WASM engine.
  • Passes preferredHighlighter: PREFERRED_HIGHLIGHTER into file, diff, and PR code viewer options across the web app.
  • Behavioral Change: All syntax highlighting across the web app switches from the previous default engine to shiki-wasm.

Macroscope summarized 6384d96.

@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20b257ea-6a69-402e-b1cb-f34e41e65fee

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

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 27, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new "every creation site must pass this value" invariant is not applied to the remaining highlighter creation site in apps/web/src/components/settings/SettingsFontPreviews.tsx. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +10 to +18
/**
* Always highlight with the Oniguruma WASM engine. The JavaScript regex engine
* can backtrack catastrophically on ordinary source lines (a Go comment
* containing `{}` and a non-ASCII dash pinned the renderer main thread for 12+
* minutes; the same input tokenizes in under 10ms on WASM). The shared
* highlighter is a first-caller-wins singleton, so every creation site must
* pass this value.
*/
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invariant documented here ("first-caller-wins singleton, so every creation site must pass this value") is not applied everywhere: apps/web/src/components/settings/SettingsFontPreviews.tsx:80-83 still calls preloadPatchFile({ patch: DIFF_PREVIEW_PATCH, options: { diffStyle: "unified", theme } }), and its own comment above states that pipeline "always awaits the shared highlighter". Appearance settings can easily be opened before any diff/file surface mounts, in which case that call wins the race and pins the JS regex engine for the whole session — exactly the hang this PR fixes.

Suggested smallest fix: add preferredHighlighter: PREFERRED_HIGHLIGHTER to the options passed to preloadPatchFile. If the SSR options type does not accept it, please note in this comment that the SSR path builds its own highlighter rather than the shared singleton, so the invariant stated here is accurate.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 5e5651cpreloadPatchFile reaches DiffHunksRenderer.initializeHighlighter(), which does create the shared singleton with the shiki-js default, so this was a real missed site. preferredHighlighter: PREFERRED_HIGHLIGHTER is now passed there too (the SSR options type accepts it). Typecheck and the 2,859 apps/web unit tests pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

macroscopeapp[bot]
macroscopeappBot previously approved these changes Aug 27, 2026
@macroscopeapp

macroscopeappBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The patch changes the highlighter implementation for every existing file, diff, preview, and worker path, adding a lazy WASM runtime dependency and altering application-wide tokenization behavior. The code is small and consistently wired, but the production blast radius warrants human review.

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

@macroscopeapp
macroscopeappBot dismissed their stale reviewAugust 27, 2026 06:44

Dismissing prior approval to re-evaluate 5e5651c

LetZicoand others added 2 commits September 1, 2026 16:07
…engine
The JavaScript regex engine backtracks catastrophically on ordinary source
lines. A 66-character Go comment line (containing braces and a non-ASCII
dash) pinned the renderer main thread for 12+ minutes when the file preview
panel restored a 476 KB / 10,915-line file: the editor's synchronous
EditorTokenizer #buildStateStack loop calls tokenizeLine per line, and its
500 ms per-line limit cannot fire because vscode-textmate only checks the
clock between regex matches — a single catastrophic match is uninterruptible.
The whole window froze until force-quit (pingdotgg#8356).
The same content tokenizes in 377 ms end-to-end on the Oniguruma WASM engine
(worst single line 9.1 ms, measured via @pierre/diffs getSharedHighlighter on
both engines). The shared highlighter is a first-caller-wins singleton, so
every creation site must name the engine: the app's getSharedHighlighter call,
the diff worker pool options, and each component options object whose
FileRenderer can lazily initialize the singleton with the library's shiki-js
default. PREFERRED_HIGHLIGHTER in syntaxHighlighting.ts names the decision
once.
The workers in DiffWorkerPoolProvider previously ran shiki-js as well, which
plausibly explains the runnable-forever DedicatedWorkers in pingdotgg#3884.
Verified: tsgo --noEmit clean; 2859 apps/web unit tests pass; production
build emits the WASM engine as a lazy ~600 KB chunk; in the dev app the full
repro file tokenizes in 385 ms in-browser where the JS engine previously hung
indefinitely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e comment
Macroscope review: SettingsFontPreviews' preloadPatchFile call reaches
DiffHunksRenderer.initializeHighlighter, which creates the shared
first-caller-wins singleton with the library's shiki-js default. Opening
Appearance settings before any diff/file surface would pin the JS regex
engine for the whole session. Pass PREFERRED_HIGHLIGHTER there too, and
trim the incident detail out of the constant's doc comment.
@t3dotgg
t3dotggforce-pushed the fix/highlighter-wasm-engine branch from 5e5651c to 6384d96CompareSeptember 1, 2026 23:09
@t3dotgg
t3dotgg merged commit feb3ea7 into pingdotgg:mainSep 2, 2026
25 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S10-29 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants

@LetZico@jetersen@t3dotgg