From 13974307400a25cec55e8167acce82f009da9b03 Mon Sep 17 00:00:00 2001 From: Ammar Date: Sun, 19 Oct 2025 13:03:08 -0500 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A4=96=20perf:=20use=20CSS=20classes?= =?UTF-8?q?=20instead=20of=20inline=20styles=20for=20syntax=20highlighting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set useInlineStyles={false} in react-syntax-highlighter to reduce JavaScript computation overhead by using CSS classes instead of computing style objects at runtime. This is a simple performance optimization that offloads styling work from JS to CSS. If highlighting is still slow, we can explore more aggressive optimizations like viewport-based rendering. Generated with `cmux` --- src/components/shared/DiffRenderer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/shared/DiffRenderer.tsx b/src/components/shared/DiffRenderer.tsx index abdfa536f..04506670f 100644 --- a/src/components/shared/DiffRenderer.tsx +++ b/src/components/shared/DiffRenderer.tsx @@ -159,6 +159,7 @@ const HighlightedContent = React.memo<{ code: string; language: string }>(({ cod Date: Sun, 19 Oct 2025 13:06:34 -0500 Subject: [PATCH 2/3] Add Prism CSS stylesheet for useInlineStyles={false} When useInlineStyles={false}, react-syntax-highlighter outputs CSS class names (.token.keyword, .token.string, etc.) instead of computing inline styles. This CSS file provides the actual styling rules. Generated from vscDarkPlus theme with backgrounds removed to preserve diff background colors in the Review tab. --- src/components/shared/DiffRenderer.tsx | 1 + src/styles/prism-syntax.css | 357 +++++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 src/styles/prism-syntax.css diff --git a/src/components/shared/DiffRenderer.tsx b/src/components/shared/DiffRenderer.tsx index 04506670f..db5e89a1f 100644 --- a/src/components/shared/DiffRenderer.tsx +++ b/src/components/shared/DiffRenderer.tsx @@ -10,6 +10,7 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { syntaxStyleNoBackgrounds } from "@/styles/syntaxHighlighting"; import { getLanguageFromPath } from "@/utils/git/languageDetector"; import { Tooltip, TooltipWrapper } from "../Tooltip"; +import "@/styles/prism-syntax.css"; // Shared type for diff line types export type DiffLineType = "add" | "remove" | "context" | "header"; diff --git a/src/styles/prism-syntax.css b/src/styles/prism-syntax.css new file mode 100644 index 000000000..b841e9d30 --- /dev/null +++ b/src/styles/prism-syntax.css @@ -0,0 +1,357 @@ +/** + * Auto-generated Prism syntax highlighting styles + * Based on VS Code Dark+ theme with backgrounds removed + * Used when react-syntax-highlighter has useInlineStyles={false} + */ + +pre[class*="language-"] { + color: #d4d4d4; + font-size: 13px; + text-shadow: none; + font-family: Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + ms-hyphens: none; + hyphens: none; + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +code[class*="language-"] { + color: #d4d4d4; + font-size: 13px; + text-shadow: none; + font-family: Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::selection { + text-shadow: none; +} + +code[class*="language-"]::selection { + text-shadow: none; +} + +pre[class*="language-"] *::selection { + text-shadow: none; +} + +code[class*="language-"] *::selection { + text-shadow: none; +} + +:not(pre) > code[class*="language-"] { + padding: .1em .3em; + border-radius: .3em; + color: #db4c69; +} + +.namespace { + -opacity: .7; +} + +doctype.doctype-tag { + color: #569CD6; +} + +doctype.name { + color: #9cdcfe; +} + +.token.comment { + color: #6a9955; +} + +.token.prolog { + color: #6a9955; +} + +.token.punctuation { + color: #d4d4d4; +} + +.language-html .language-css .token.punctuation { + color: #d4d4d4; +} + +.language-html .language-javascript .token.punctuation { + color: #d4d4d4; +} + +.token.property { + color: #9cdcfe; +} + +.token.tag { + color: #569cd6; +} + +.token.boolean { + color: #569cd6; +} + +.token.number { + color: #b5cea8; +} + +.token.constant { + color: #9cdcfe; +} + +.token.symbol { + color: #b5cea8; +} + +.token.inserted { + color: #b5cea8; +} + +.token.unit { + color: #b5cea8; +} + +.token.selector { + color: #d7ba7d; +} + +.token.attr-name { + color: #9cdcfe; +} + +.token.string { + color: #ce9178; +} + +.token.char { + color: #ce9178; +} + +.token.builtin { + color: #ce9178; +} + +.token.deleted { + color: #ce9178; +} + +.language-css .token.string.url { + text-decoration: underline; +} + +.token.operator { + color: #d4d4d4; +} + +.token.entity { + color: #569cd6; +} + +operator.arrow { + color: #569CD6; +} + +.token.atrule { + color: #ce9178; +} + +atrule.rule { + color: #c586c0; +} + +atrule.url { + color: #9cdcfe; +} + +atrule.url.function { + color: #dcdcaa; +} + +atrule.url.punctuation { + color: #d4d4d4; +} + +.token.keyword { + color: #569CD6; +} + +keyword.module { + color: #c586c0; +} + +keyword.control-flow { + color: #c586c0; +} + +.token.function { + color: #dcdcaa; +} + +function.maybe-class-name { + color: #dcdcaa; +} + +.token.regex { + color: #d16969; +} + +.token.important { + color: #569cd6; +} + +.token.italic { + font-style: italic; +} + +.token.class-name { + color: #4ec9b0; +} + +.token.maybe-class-name { + color: #4ec9b0; +} + +.token.console { + color: #9cdcfe; +} + +.token.parameter { + color: #9cdcfe; +} + +.token.interpolation { + color: #9cdcfe; +} + +punctuation.interpolation-punctuation { + color: #569cd6; +} + +.token.variable { + color: #9cdcfe; +} + +imports.maybe-class-name { + color: #9cdcfe; +} + +exports.maybe-class-name { + color: #9cdcfe; +} + +.token.escape { + color: #d7ba7d; +} + +tag.punctuation { + color: #808080; +} + +.token.cdata { + color: #808080; +} + +.token.attr-value { + color: #ce9178; +} + +attr-value.punctuation { + color: #ce9178; +} + +attr-value.punctuation.attr-equals { + color: #d4d4d4; +} + +.token.namespace { + color: #4ec9b0; +} + +pre[class*="language-javascript"] { + color: #9cdcfe; +} + +code[class*="language-javascript"] { + color: #9cdcfe; +} + +pre[class*="language-jsx"] { + color: #9cdcfe; +} + +code[class*="language-jsx"] { + color: #9cdcfe; +} + +pre[class*="language-typescript"] { + color: #9cdcfe; +} + +code[class*="language-typescript"] { + color: #9cdcfe; +} + +pre[class*="language-tsx"] { + color: #9cdcfe; +} + +code[class*="language-tsx"] { + color: #9cdcfe; +} + +pre[class*="language-css"] { + color: #ce9178; +} + +code[class*="language-css"] { + color: #ce9178; +} + +pre[class*="language-html"] { + color: #d4d4d4; +} + +code[class*="language-html"] { + color: #d4d4d4; +} + +.language-regex .token.anchor { + color: #dcdcaa; +} + +.language-html .token.punctuation { + color: #808080; +} + +pre[class*="language-"] > code[class*="language-"] { + position: relative; + z-index: 1; +} + +.line-highlight.line-highlight { + box-shadow: inset 5px 0 0 #f7d87c; + z-index: 0; +} + From 784a551e604faf277e891a80248d2a811ffbbf6a Mon Sep 17 00:00:00 2001 From: Ammar Date: Sun, 19 Oct 2025 13:09:57 -0500 Subject: [PATCH 3/3] Add generation script for Prism CSS and omit font styles - Created scripts/generate_prism_css.ts to generate CSS from vscDarkPlus theme - Filters out font-family and font-size to inherit from parent (DiffContainer) - Preserves project font standards: var(--font-monospace) and 12px default - Updated CSS with regeneration instructions --- scripts/generate_prism_css.ts | 93 +++++++++++++++++++++++++++++++++++ src/styles/prism-syntax.css | 9 ++-- 2 files changed, 97 insertions(+), 5 deletions(-) create mode 100755 scripts/generate_prism_css.ts diff --git a/scripts/generate_prism_css.ts b/scripts/generate_prism_css.ts new file mode 100755 index 000000000..d95ea9366 --- /dev/null +++ b/scripts/generate_prism_css.ts @@ -0,0 +1,93 @@ +#!/usr/bin/env bun + +/** + * Generates Prism CSS stylesheet from vscDarkPlus theme + * Used for syntax highlighting when react-syntax-highlighter has useInlineStyles={false} + * + * Strips backgrounds to preserve diff backgrounds in Review tab + * Omits font-family and font-size to inherit from parent components + */ + +import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"; +import type { CSSProperties } from "react"; + +const OUTPUT_PATH = "src/styles/prism-syntax.css"; + +// Strip backgrounds like we do in syntaxHighlighting.ts +const syntaxStyleNoBackgrounds: Record = {}; +for (const [key, value] of Object.entries(vscDarkPlus as Record)) { + if (typeof value === "object" && value !== null) { + const { background, backgroundColor, ...rest } = value as Record; + if (Object.keys(rest).length > 0) { + syntaxStyleNoBackgrounds[key] = rest as CSSProperties; + } + } +} + +// Convert CSS properties object to CSS string +function cssPropertiesToString(props: CSSProperties): string { + return Object.entries(props) + .filter(([key]) => { + // Skip font-family and font-size - we want to inherit these + return key !== "fontFamily" && key !== "fontSize"; + }) + .map(([key, value]) => { + // Convert camelCase to kebab-case + const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase(); + return ` ${cssKey}: ${value};`; + }) + .join("\n"); +} + +// Generate CSS content +function generateCSS(): string { + const lines: string[] = [ + "/**", + " * Auto-generated Prism syntax highlighting styles", + " * Based on VS Code Dark+ theme with backgrounds removed", + " * Used when react-syntax-highlighter has useInlineStyles={false}", + " *", + " * Font family and size are intentionally omitted to inherit from parent.", + " * ", + " * To regenerate: bun run scripts/generate_prism_css.ts", + " */", + "", + ]; + + for (const [selector, props] of Object.entries(syntaxStyleNoBackgrounds)) { + const cssRules = cssPropertiesToString(props); + if (cssRules.trim().length > 0) { + // Handle selectors that need .token prefix + let cssSelector = selector; + + // Add .token prefix for single-word selectors (token types) + if (!/[ >[\]:.]/.test(selector) && !selector.startsWith("pre") && !selector.startsWith("code")) { + cssSelector = `.token.${selector}`; + } + + lines.push(`${cssSelector} {`); + lines.push(cssRules); + lines.push("}"); + lines.push(""); + } + } + + return lines.join("\n"); +} + +async function main() { + console.log("Generating Prism CSS stylesheet..."); + + const css = generateCSS(); + + console.log(`Writing CSS to ${OUTPUT_PATH}...`); + await Bun.write(OUTPUT_PATH, css); + + console.log("✓ Prism CSS generated successfully"); +} + +main().catch((error) => { + console.error("Error generating Prism CSS:", error); + process.exit(1); +}); + diff --git a/src/styles/prism-syntax.css b/src/styles/prism-syntax.css index b841e9d30..ee31f9fbe 100644 --- a/src/styles/prism-syntax.css +++ b/src/styles/prism-syntax.css @@ -2,13 +2,15 @@ * Auto-generated Prism syntax highlighting styles * Based on VS Code Dark+ theme with backgrounds removed * Used when react-syntax-highlighter has useInlineStyles={false} + * + * Font family and size are intentionally omitted to inherit from parent. + * + * To regenerate: bun run scripts/generate_prism_css.ts */ pre[class*="language-"] { color: #d4d4d4; - font-size: 13px; text-shadow: none; - font-family: Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace; direction: ltr; text-align: left; white-space: pre; @@ -29,9 +31,7 @@ pre[class*="language-"] { code[class*="language-"] { color: #d4d4d4; - font-size: 13px; text-shadow: none; - font-family: Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace; direction: ltr; text-align: left; white-space: pre; @@ -354,4 +354,3 @@ pre[class*="language-"] > code[class*="language-"] { box-shadow: inset 5px 0 0 #f7d87c; z-index: 0; } -