From 46339a040a6040ea66b899047c41b50b994ab6ce Mon Sep 17 00:00:00 2001 From: Hikari Date: Fri, 6 Mar 2026 18:03:42 -0800 Subject: [PATCH] perf: virtual windowing, markdown memoisation, and search debounce - Terminal: virtual windowing renders max 150 lines, loads 50 older on scroll-up with scroll position compensation; auto-advances window forward during auto-scroll so old DOM nodes are unloaded continuously - Markdown: two-stage derived rendering separates expensive parse step (marked + hljs + spoilers, runs on content change) from cheap search highlight step (runs on query change only) - Achievements: fix double Object.keys() call in derived store - Terminal: 150ms debounce on search query to reduce redundant updates - Tests: add Markdown.test.ts for processSpoilers and highlightSearchMatches; extend Terminal.test.ts with virtual windowing helper coverage --- src/lib/components/Markdown.svelte | 18 +-- src/lib/components/Markdown.test.ts | 163 ++++++++++++++++++++++++++++ src/lib/components/Terminal.svelte | 68 +++++++++++- src/lib/components/Terminal.test.ts | 104 ++++++++++++++++++ src/lib/stores/achievements.ts | 13 ++- 5 files changed, 349 insertions(+), 17 deletions(-) create mode 100644 src/lib/components/Markdown.test.ts diff --git a/src/lib/components/Markdown.svelte b/src/lib/components/Markdown.svelte index 205fb6a..6786210 100644 --- a/src/lib/components/Markdown.svelte +++ b/src/lib/components/Markdown.svelte @@ -108,15 +108,19 @@ return processed; } - function renderMarkdown(text: string): string { + // Two-stage reactive rendering: + // Stage 1 — only re-runs when `content` changes (expensive: marked + hljs + spoilers) + let parsedHtml = $derived.by(() => { try { - const html = marked.parse(text) as string; - const withSpoilers = processSpoilers(html); - return highlightSearchMatches(withSpoilers, searchQuery); + const html = marked.parse(content) as string; + return processSpoilers(html); } catch { - return text; + return content; } - } + }); + + // Stage 2 — re-runs when search changes; skips re-parsing markdown entirely + let renderedHtml = $derived(highlightSearchMatches(parsedHtml, searchQuery)); function handleSpoilerClick(event: Event) { const target = event.target as HTMLElement; @@ -191,7 +195,7 @@ role="presentation" > - {@html renderMarkdown(content)} + {@html renderedHtml}