From 94f940cddc9ca39d484996616f9f4b322c1ff7f8 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Sun, 1 Mar 2026 11:49:55 +0900
Subject: fix(frontend): render line numbers before syntax highlighting to
 prevent layout shift

Show plaintext with Shiki-compatible line structure (.shiki > code > .line)
from the start, so CSS line-number counters apply immediately. This prevents
the popover from jittering when Shiki replaces the content. If Shiki fails,
the plaintext with line numbers remains as a natural fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 frontend/app/components/Gaming/CodeBlock.tsx | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)

(limited to 'frontend/app/components/Gaming/CodeBlock.tsx')

diff --git a/frontend/app/components/Gaming/CodeBlock.tsx b/frontend/app/components/Gaming/CodeBlock.tsx
index 49ef01e..2107f94 100644
--- a/frontend/app/components/Gaming/CodeBlock.tsx
+++ b/frontend/app/components/Gaming/CodeBlock.tsx
@@ -8,12 +8,30 @@ type Props = {
 	language: BundledLanguage;
 };
 
+function Plaintext({ code }: { code: string }) {
+	const lines = code.split("\n");
+	return (
+		<pre>
+			<code>
+				{lines.map((line, i) => (
+					<span key={i} className="line">
+						{line}
+						{i < lines.length - 1 ? "\n" : ""}
+					</span>
+				))}
+			</code>
+		</pre>
+	);
+}
+
 export default function CodeBlock({ code, language }: Props) {
 	const [nodes, setNodes] = useState<JSX.Element | null>(null);
 	const [showCopied, setShowCopied] = useState(false);
 
 	useLayoutEffect(() => {
-		highlight(code, language).then(setNodes);
+		highlight(code, language)
+			.then(setNodes)
+			.catch(() => setNodes(null));
 	}, [code, language]);
 
 	const handleCopy = () => {
@@ -37,9 +55,9 @@ export default function CodeBlock({ code, language }: Props) {
 					)}
 				</button>
 			)}
-			<pre className="h-full w-full p-2 pr-12 bg-white rounded-lg border border-gray-300 whitespace-pre-wrap break-words">
-				{nodes === null ? <code>{code}</code> : nodes}
-			</pre>
+			<div className="shiki h-full w-full p-2 pr-12 bg-white rounded-lg border border-gray-300 whitespace-pre-wrap break-words">
+				{nodes ?? <Plaintext code={code} />}
+			</div>
 		</div>
 	);
 }
-- 
cgit v1.3.1

