From cb069a45be853fbbb1487ff310ced9033fcdcb3b Mon Sep 17 00:00:00 2001 From: Yandrik Date: Wed, 9 Jul 2025 14:20:45 +0200 Subject: [PATCH] fixes and better mermaid --- App.tsx | 83 +++++++++++++++++++++++---------- data/swa_exercises.7.ts | 2 +- make-practiceapp-deployready.md | 7 +++ 3 files changed, 67 insertions(+), 25 deletions(-) diff --git a/App.tsx b/App.tsx index b1909a5..217efce 100644 --- a/App.tsx +++ b/App.tsx @@ -27,37 +27,51 @@ import mermaid from 'mermaid'; const extractJson = (text: string): any => { let jsonString = text.trim(); + // Remove BOM and other invisible characters + jsonString = jsonString.replace(/^\uFEFF/, ''); // Remove BOM + jsonString = jsonString.replace(/^[\u200B-\u200D\uFEFF]/g, ''); // Remove zero-width characters + const fenceRegex = /^```(?:json)?\s*\n?(.*?)\n?\s*```$/s; const match = jsonString.match(fenceRegex); if (match && match[1]) { jsonString = match[1].trim(); } - const lastBracket = jsonString.lastIndexOf('}'); - if (lastBracket === -1) { - throw new Error("No closing brace '}' found in API response."); - } - - let openBraceCount = 0; - let firstBracket = -1; - for (let i = lastBracket; i >= 0; i--) { - if (jsonString[i] === '}') { - openBraceCount++; - } else if (jsonString[i] === '{') { - openBraceCount--; + // Try to parse directly first (most common case) + try { + return JSON.parse(jsonString); + } catch (directParseError) { + // If direct parsing fails, try the bracket extraction method + const lastBracket = jsonString.lastIndexOf('}'); + if (lastBracket === -1) { + throw new Error("No closing brace '}' found in API response."); } - if (openBraceCount === 0) { - firstBracket = i; - break; + + let openBraceCount = 0; + let firstBracket = -1; + for (let i = lastBracket; i >= 0; i--) { + if (jsonString[i] === '}') { + openBraceCount++; + } else if (jsonString[i] === '{') { + openBraceCount--; + } + if (openBraceCount === 0) { + firstBracket = i; + break; + } + } + + if (firstBracket === -1) { + throw new Error("Could not find matching opening brace '{' in API response."); + } + + const finalJsonString = jsonString.substring(firstBracket, lastBracket + 1); + try { + return JSON.parse(finalJsonString); + } catch (finalParseError) { + throw new Error(`JSON parsing failed: ${finalParseError.message}. Raw text: ${jsonString.substring(0, 100)}...`); } } - - if (firstBracket === -1) { - throw new Error("Could not find matching opening brace '{' in API response."); - } - - const finalJsonString = jsonString.substring(firstBracket, lastBracket + 1); - return JSON.parse(finalJsonString); }; @@ -184,15 +198,23 @@ const MermaidDiagram: React.FC<{ value: string; onChange: (value: string) => voi useEffect(() => { if (value.trim()) { const renderDiagram = async () => { + renderCountRef.current += 1; + const uniqueId = `mermaid-diagram-${renderCountRef.current}`; + try { setError(''); - renderCountRef.current += 1; - const uniqueId = `mermaid-diagram-${renderCountRef.current}`; + setDiagramHtml(''); const { svg } = await mermaid.render(uniqueId, value); setDiagramHtml(svg); } catch (err) { setError(err instanceof Error ? err.message : 'Mermaid diagram rendering failed'); setDiagramHtml(''); + } finally { + // Clean up the DOM element that mermaid might have created + const element = document.getElementById(uniqueId); + if (element && element.parentNode) { + element.parentNode.removeChild(element); + } } }; renderDiagram(); @@ -201,6 +223,19 @@ const MermaidDiagram: React.FC<{ value: string; onChange: (value: string) => voi setError(''); } }, [value]); + + // Cleanup function to remove any lingering mermaid elements + useEffect(() => { + return () => { + // Clean up any mermaid elements that might be left in the DOM + const mermaidElements = document.querySelectorAll('[id^="mermaid-diagram-"]'); + mermaidElements.forEach(element => { + if (element.parentNode) { + element.parentNode.removeChild(element); + } + }); + }; + }, []); return (
diff --git a/data/swa_exercises.7.ts b/data/swa_exercises.7.ts index ef1c901..283fc00 100644 --- a/data/swa_exercises.7.ts +++ b/data/swa_exercises.7.ts @@ -3,7 +3,7 @@ import { Exercise } from '../types'; export const swaExercises7: Exercise[] = [ { - id: 'swa-6-1-ddd-tactical', + id: 'swa-7-1-ddd-tactical', title: '6.1 - SWA - Domain-Driven Design (Tactical)', parts: [ { diff --git a/make-practiceapp-deployready.md b/make-practiceapp-deployready.md index 78bef1a..c88a190 100644 --- a/make-practiceapp-deployready.md +++ b/make-practiceapp-deployready.md @@ -512,9 +512,16 @@ CMD [ "serve", "-s", "dist", "-l", "8000" ] - **Real-time Preview**: Users can see their Mermaid diagram rendered as they type - **Error Handling**: Clear error messages for invalid Mermaid syntax - **Unique Rendering**: Each render uses a unique ID to prevent caching issues that could cause diagrams to disappear +- **DOM Cleanup**: Proper cleanup of mermaid-generated DOM elements to prevent accumulation of leftover elements from failed renders - **Consistent Styling**: Matches the existing design language of the application - **Accessibility**: Proper ARIA labels and semantic HTML structure +**Important Implementation Details**: + +- **DOM Element Cleanup**: Added `finally` block in render function to clean up DOM elements that mermaid creates, especially important for failed renders which would otherwise leave orphaned elements +- **Component Unmount Cleanup**: Added cleanup `useEffect` to remove any lingering mermaid elements when component unmounts +- **Unique ID Generation**: Uses `useRef` counter to generate unique IDs for each render attempt, preventing mermaid's internal caching issues + This enhancement makes the application more suitable for software architecture exercises that require diagram creation and visualization. ---