Blogging platform with advanced tools for arts and sciences.
6

Configure Feed

Select the types of activity you want to include in your feed.

Fix bug that prevented code blocks in dark mode to be presented correctly

lemma (Jun 3, 2026, 11:21 PM -0500) 5e09a046 414575e1

+20 -4
+20 -4
app/components/CodeMirrorMarkdownEditor.tsx
··· 62 62 // which is what applyTheme() toggles. This correctly reflects explicit 63 63 // user overrides, not just the OS-level prefers-color-scheme. 64 64 const useSystemTheme = () => { 65 - const [theme, setTheme] = useState<'light' | 'dark'>('light') 65 + // Read the DOM directly so the initial render uses the correct theme. 66 + // The inline script in root.tsx applies the `dark` class synchronously 67 + // before React mounts, so this lazy initializer always sees the right value. 68 + const [theme, setTheme] = useState<'light' | 'dark'>(() => 69 + document.documentElement.classList.contains('dark') ? 'dark' : 'light' 70 + ) 66 71 67 72 useEffect(() => { 68 73 const isDark = () => document.documentElement.classList.contains('dark') 69 - setTheme(isDark() ? 'dark' : 'light') 70 74 71 75 const observer = new MutationObserver(() => { 72 76 setTheme(isDark() ? 'dark' : 'light') ··· 91 95 const [imageError, setImageError] = useState<string | null>(null) 92 96 const imageErrorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null) 93 97 const systemTheme = useSystemTheme() 98 + const processGenRef = useRef(0) 94 99 const editorParentRef = useRef<HTMLDivElement | null>(null) 95 100 const previewRef = useRef<HTMLDivElement | null>(null) 96 101 const viewRef = useRef<EditorView | null>(null) ··· 131 136 imageErrorTimerRef.current = setTimeout(() => setImageError(null), 4000) 132 137 } 133 138 134 - // Process markdown to HTML using shared processor 139 + // Process markdown to HTML using shared processor. 140 + // processGenRef is a generation counter that guards against stale async results. 141 + // Two calls can be in-flight simultaneously — most commonly on page load when 142 + // useSystemTheme's initial render uses the correct theme but a theme-change 143 + // effect can still fire and kick off a second call. Shiki loads each theme's 144 + // highlighter lazily and concurrently, so the earlier call can resolve last 145 + // and overwrite the correct HTML. Stamping each call with the current counter 146 + // and discarding any result whose stamp no longer matches ensures only the 147 + // most recently dispatched call wins. 135 148 const processMarkdownContent = useCallback(async (markdownText: string) => { 149 + const gen = ++processGenRef.current 136 150 setIsProcessing(true) 137 151 try { 138 152 const htmlString = await processMarkdown(markdownText, systemTheme) 153 + if (gen !== processGenRef.current) return // a newer call is already in flight; discard 139 154 setHtml(htmlString) 140 155 } catch (error) { 156 + if (gen !== processGenRef.current) return 141 157 logger.error({ error }, 'Error processing Markdown.') 142 158 // Use safe HTML for error message - no user content included 143 159 setHtml('<div style="color: red; padding: 1rem; border: 1px solid red; border-radius: 4px; background: rgba(255,0,0,0.1);">⚠️ Error processing markdown. Please check your syntax.</div>') 144 160 } finally { 145 - setIsProcessing(false) 161 + if (gen === processGenRef.current) setIsProcessing(false) 146 162 } 147 163 }, [systemTheme]) 148 164