From 71a9d1be64366248e9a9ba122029207c42631010 Mon Sep 17 00:00:00 2001 From: Narutama Aurum Date: Mon, 1 Jun 2026 18:11:24 +0800 Subject: [PATCH 1/3] 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/3] 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/3] 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; };