From 71a9d1be64366248e9a9ba122029207c42631010 Mon Sep 17 00:00:00 2001 From: Narutama Aurum Date: Mon, 1 Jun 2026 18:11:24 +0800 Subject: [PATCH 1/5] fix: address missing hook dependencies to remove exhaustive-deps disables - terminal.tsx: Remove unnecessary eslint-disable comments (deps already correct) - playground-editor.tsx: Wrap updateEditorLanguage in useCallback - code-line.tsx: Move highlight/highlightCode outside component body Closes #442 --- modules/home/code-line.tsx | 40 +++++++------------ .../components/playground-editor.tsx | 7 ++-- modules/webcontainers/components/terminal.tsx | 2 - 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/modules/home/code-line.tsx b/modules/home/code-line.tsx index 06da7478..820f4cee 100644 --- a/modules/home/code-line.tsx +++ b/modules/home/code-line.tsx @@ -2,38 +2,28 @@ "use client"; import React from 'react'; -export const CodeLine = ({ line }: { line: string }) => { - // Basic replacements to simulate syntax highlighting - // Note: This is a very simplistic implementation and should be replaced with a proper library like prismjs or shiki in production - - const highlight = (text: string) => { - // We use a series of replacements. Order matters to avoid replacing inside already replaced spans. - // A better approach for robust highlighting is tokenization. - - const highlighted = text; - - // Comments (simple // for now) - if (highlighted.includes('//')) { - const parts = highlighted.split('//'); - return <>{'//' + parts[1]}; - } - - return ; - }; +const highlightCode = (code: string) => { + return code + .replace(/import|from|export|default|return|const|new/g, '$&') + .replace(/'[^']*'/g, '$&') + .replace(/"[^"]*"/g, '$&') + .replace(/Editron|console|editor/g, '$&'); +}; - const highlightCode = (code: string) => { - return code - .replace(/import|from|export|default|return|const|new/g, '$&') - .replace(/'[^']*'/g, '$&') - .replace(/"[^"]*"/g, '$&') - .replace(/Editron|console|editor/g, '$&'); +const highlight = (text: string) => { + const highlighted = text; + if (highlighted.includes('//')) { + const parts = highlighted.split('//'); + return <>{'//' + parts[1]}; } + return ; +}; +export const CodeLine = ({ line }: { line: string }) => { const [highlighted, setHighlighted] = React.useState(line); React.useEffect(() => { setHighlighted(highlight(line)); -// eslint-disable-next-line react-hooks/exhaustive-deps }, [line]); return highlighted; diff --git a/modules/playground/components/playground-editor.tsx b/modules/playground/components/playground-editor.tsx index 1434060a..a9a64804 100644 --- a/modules/playground/components/playground-editor.tsx +++ b/modules/playground/components/playground-editor.tsx @@ -253,7 +253,7 @@ const PlaygroundEditor = ({ ); }; - const updateEditorLanguage = () => { + const updateEditorLanguage = React.useCallback(() => { if (!activeFile || !monacoRef.current || !editorRef.current) return; const model = editorRef.current.getModel(); if (!model) return; @@ -264,12 +264,11 @@ const PlaygroundEditor = ({ } catch (error) { console.warn("Failed to set editor language:", error); } - }; + }, [activeFile]); useEffect(() => { updateEditorLanguage(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeFile]); + }, [activeFile, updateEditorLanguage]); // Bind Yjs to Monaco useEffect(() => { diff --git a/modules/webcontainers/components/terminal.tsx b/modules/webcontainers/components/terminal.tsx index 743a32fd..4154e015 100644 --- a/modules/webcontainers/components/terminal.tsx +++ b/modules/webcontainers/components/terminal.tsx @@ -325,7 +325,6 @@ const writePrompt(); return terminal; -// eslint-disable-next-line react-hooks/exhaustive-deps }, [theme, handleTerminalInput, writePrompt]); const connectToWebContainer = useCallback(async () => { @@ -420,7 +419,6 @@ const currentProcess.current.kill(); } if (shellProcess.current) { -// eslint-disable-next-line react-hooks/exhaustive-deps shellProcess.current.kill(); } if (term.current) { From 5473a53e63a61e0b1ad932671caf5150f136c78a Mon Sep 17 00:00:00 2001 From: Narutama Aurum Date: Tue, 9 Jun 2026 07:44:39 +0800 Subject: [PATCH 2/5] fix: address review comments on PR #447 - Fix missing React import: add useCallback to named imports in playground-editor.tsx - Remove redundant variable assignment in code-line.tsx - Add HTML escaping to prevent XSS in dangerouslySetInnerHTML usage --- modules/home/code-line.tsx | 19 ++++++++++++++----- .../components/playground-editor.tsx | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/home/code-line.tsx b/modules/home/code-line.tsx index 820f4cee..e2dcfde5 100644 --- a/modules/home/code-line.tsx +++ b/modules/home/code-line.tsx @@ -2,8 +2,18 @@ "use client"; import React from 'react'; +const escapeHtml = (text: string) => { + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +}; + const highlightCode = (code: string) => { - return code + const escaped = escapeHtml(code); + return escaped .replace(/import|from|export|default|return|const|new/g, '$&') .replace(/'[^']*'/g, '$&') .replace(/"[^"]*"/g, '$&') @@ -11,12 +21,11 @@ const highlightCode = (code: string) => { }; const highlight = (text: string) => { - const highlighted = text; - if (highlighted.includes('//')) { - const parts = highlighted.split('//'); + if (text.includes('//')) { + const parts = text.split('//'); return <>{'//' + parts[1]}; } - return ; + return ; }; export const CodeLine = ({ line }: { line: string }) => { diff --git a/modules/playground/components/playground-editor.tsx b/modules/playground/components/playground-editor.tsx index a9a64804..c70601e1 100644 --- a/modules/playground/components/playground-editor.tsx +++ b/modules/playground/components/playground-editor.tsx @@ -1,7 +1,7 @@ "use client"; import { EDITOR_CONFIG } from "@/lib/constants/config"; -import { useRef, useEffect, useState } from "react"; +import { useRef, useEffect, useState, useCallback } from "react"; import Editor, { type Monaco } from "@monaco-editor/react"; import type { editor as MonacoEditor } from "monaco-editor"; import { @@ -253,7 +253,7 @@ const PlaygroundEditor = ({ ); }; - const updateEditorLanguage = React.useCallback(() => { + const updateEditorLanguage = useCallback(() => { if (!activeFile || !monacoRef.current || !editorRef.current) return; const model = editorRef.current.getModel(); if (!model) return; From af940f8a4fb477403d9cc58900af64d9c88d8db8 Mon Sep 17 00:00:00 2001 From: Narutama Aurum Date: Tue, 9 Jun 2026 08:35:01 +0800 Subject: [PATCH 3/5] fix: use useMemo instead of useState+useEffect for derived state in CodeLine Addresses CodeRabbit nitpick: highlighted is a pure synchronous transformation of the line prop, so useMemo avoids an extra render per CodeLine instance. --- modules/home/code-line.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/home/code-line.tsx b/modules/home/code-line.tsx index e2dcfde5..bbc4742d 100644 --- a/modules/home/code-line.tsx +++ b/modules/home/code-line.tsx @@ -29,11 +29,6 @@ const highlight = (text: string) => { }; export const CodeLine = ({ line }: { line: string }) => { - const [highlighted, setHighlighted] = React.useState(line); - - React.useEffect(() => { - setHighlighted(highlight(line)); - }, [line]); - + const highlighted = React.useMemo(() => highlight(line), [line]); return highlighted; }; From 286264d3cc17c8e3d53afd2c8e9918c0d32a3f80 Mon Sep 17 00:00:00 2001 From: Narutama Aurum Date: Tue, 9 Jun 2026 09:41:23 +0800 Subject: [PATCH 4/5] fix: address CodeRabbit review comments on code-line.tsx - Fix comment parsing to preserve content after additional '//' by using indexOf instead of split - Fix string token highlighting by applying regexes on raw code before escaping HTML entities - Use placeholder approach to preserve span tags during escaping --- modules/home/code-line.tsx | 46 ++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/modules/home/code-line.tsx b/modules/home/code-line.tsx index bbc4742d..3c10afcb 100644 --- a/modules/home/code-line.tsx +++ b/modules/home/code-line.tsx @@ -1,4 +1,3 @@ - "use client"; import React from 'react'; @@ -12,18 +11,47 @@ const escapeHtml = (text: string) => { }; const highlightCode = (code: string) => { - const escaped = escapeHtml(code); - return escaped - .replace(/import|from|export|default|return|const|new/g, '$&') - .replace(/'[^']*'/g, '$&') - .replace(/"[^"]*"/g, '$&') - .replace(/Editron|console|editor/g, '$&'); + // Use placeholders for span tags to preserve them during escaping + const placeholderPrefix = "__SPAN_"; + const placeholderSuffix = "__"; + let placeholderIndex = 0; + const placeholders: string[] = []; + + const replaceWithPlaceholder = (match: string) => { + const placeholder = `${placeholderPrefix}${placeholderIndex++}${placeholderSuffix}`; + placeholders.push(match); + return placeholder; + }; + + // Apply syntax highlighting on raw code first + let highlighted = code + .replace(/import|from|export|default|return|const|new/g, (match) => replaceWithPlaceholder(`${match}`)) + .replace(/'[^']*'/g, (match) => replaceWithPlaceholder(`${match}`)) + .replace(/"[^"]*"/g, (match) => replaceWithPlaceholder(`${match}`)) + .replace(/Editron|console|editor/g, (match) => replaceWithPlaceholder(`${match}`)); + + // Escape HTML entities in the non-span content + highlighted = escapeHtml(highlighted); + + // Restore the span tags from placeholders + placeholders.forEach((span, index) => { + highlighted = highlighted.replace(`${placeholderPrefix}${index}${placeholderSuffix}`, span); + }); + + return highlighted; }; const highlight = (text: string) => { if (text.includes('//')) { - const parts = text.split('//'); - return <>{'//' + parts[1]}; + const commentStart = text.indexOf('//'); + const codePart = text.slice(0, commentStart); + const commentPart = text.slice(commentStart); + return ( + <> + + {commentPart} + + ); } return ; }; From 8f991cc0cdafc58967e404e19dbfcdc24cbca125 Mon Sep 17 00:00:00 2001 From: Narutama Aurum Date: Tue, 9 Jun 2026 20:02:40 +0800 Subject: [PATCH 5/5] fix: address CodeRabbit review comments on code-line.tsx and playground-editor.tsx - Add word boundaries to regex patterns in code-line.tsx to prevent substring matching - Escape matched text before inserting into spans to prevent XSS - Simplify useEffect dependency array in playground-editor.tsx (remove redundant activeFile) Closes #481 --- modules/home/code-line.tsx | 10 +++++----- modules/playground/components/playground-editor.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/home/code-line.tsx b/modules/home/code-line.tsx index 3c10afcb..62b8dff7 100644 --- a/modules/home/code-line.tsx +++ b/modules/home/code-line.tsx @@ -25,10 +25,10 @@ const highlightCode = (code: string) => { // Apply syntax highlighting on raw code first let highlighted = code - .replace(/import|from|export|default|return|const|new/g, (match) => replaceWithPlaceholder(`${match}`)) - .replace(/'[^']*'/g, (match) => replaceWithPlaceholder(`${match}`)) - .replace(/"[^"]*"/g, (match) => replaceWithPlaceholder(`${match}`)) - .replace(/Editron|console|editor/g, (match) => replaceWithPlaceholder(`${match}`)); + .replace(/\b(?:import|from|export|default|return|const|new)\b/g, (match) => replaceWithPlaceholder(`${escapeHtml(match)}`)) + .replace(/'[^']*'/g, (match) => replaceWithPlaceholder(`${escapeHtml(match)}`)) + .replace(/\"[^\"]*\"/g, (match) => replaceWithPlaceholder(`${escapeHtml(match)}`)) + .replace(/\b(?:Editron|console|editor)\b/g, (match) => replaceWithPlaceholder(`${escapeHtml(match)}`)); // Escape HTML entities in the non-span content highlighted = escapeHtml(highlighted); @@ -59,4 +59,4 @@ const highlight = (text: string) => { export const CodeLine = ({ line }: { line: string }) => { const highlighted = React.useMemo(() => highlight(line), [line]); return highlighted; -}; +}; \ No newline at end of file diff --git a/modules/playground/components/playground-editor.tsx b/modules/playground/components/playground-editor.tsx index c70601e1..6d4735b5 100644 --- a/modules/playground/components/playground-editor.tsx +++ b/modules/playground/components/playground-editor.tsx @@ -268,7 +268,7 @@ const PlaygroundEditor = ({ useEffect(() => { updateEditorLanguage(); - }, [activeFile, updateEditorLanguage]); + }, [updateEditorLanguage]); // Bind Yjs to Monaco useEffect(() => {