From c9c61a279ec8a02be65e09b4d11873f77d570bae Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Sat, 28 Feb 2026 16:26:10 -0600 Subject: [PATCH] Bound document cache size to prevent long-session UI slowdown --- Journal.App/src/routes/+page.svelte | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Journal.App/src/routes/+page.svelte b/Journal.App/src/routes/+page.svelte index 72903bf..79dcba4 100644 --- a/Journal.App/src/routes/+page.svelte +++ b/Journal.App/src/routes/+page.svelte @@ -58,6 +58,7 @@ busy: boolean; error: string; }; + const MAX_CACHED_DOCUMENTS = 5; const initialEntry = getDefaultEntry(get(entriesStore)); @@ -73,6 +74,7 @@ "# Daily Notes\n\nStart writing today's entry...", }; let savedDocumentContent: Record = { ...openDocuments }; + let documentAccessOrder: string[] = Object.keys(openDocuments); let modalOpen = false; let modalTitle = ""; let modalMessage = ""; @@ -165,6 +167,41 @@ return fileName.replace(/\.template\.md$/i, ""); } + function touchDocument(id: string) { + documentAccessOrder = [ + ...documentAccessOrder.filter((item) => item !== id), + id, + ]; + } + + function pruneDocumentCache(preserveIds: string[] = []) { + const preserve = new Set( + preserveIds.filter(Boolean).concat(activeDocumentId ? [activeDocumentId] : []), + ); + const cachedIds = Object.keys(openDocuments); + if (cachedIds.length <= MAX_CACHED_DOCUMENTS) return; + + let nextOpen = { ...openDocuments }; + let nextSaved = { ...savedDocumentContent }; + let nextOrder = [...documentAccessOrder]; + let currentSize = cachedIds.length; + + for (const candidate of documentAccessOrder) { + if (currentSize <= MAX_CACHED_DOCUMENTS) break; + if (preserve.has(candidate)) continue; + if (!(candidate in nextOpen)) continue; + + delete nextOpen[candidate]; + delete nextSaved[candidate]; + nextOrder = nextOrder.filter((item) => item !== candidate); + currentSize -= 1; + } + + openDocuments = nextOpen; + savedDocumentContent = nextSaved; + documentAccessOrder = nextOrder; + } + function showModal(options: { action: | "logout-confirm" @@ -361,6 +398,9 @@ openDocuments = rest; const { [activeDocumentId]: __, ...savedRest } = savedDocumentContent; savedDocumentContent = savedRest; + documentAccessOrder = documentAccessOrder.filter( + (item) => item !== activeDocumentId, + ); activeDocumentId = ""; activeDocumentLabel = ""; editMode = false; @@ -398,6 +438,7 @@ ...savedRest, [saved.id]: saved.initialContent, }; + touchDocument(saved.id); activeDocumentId = saved.id; activeDocumentLabel = saved.label; } else { @@ -405,6 +446,7 @@ ...savedDocumentContent, [activeDocumentId]: content, }; + touchDocument(activeDocumentId); } } else if ( selectedSection === "lists" && @@ -416,7 +458,9 @@ ...savedDocumentContent, [activeDocumentId]: content, }; + touchDocument(activeDocumentId); } + pruneDocumentCache([activeDocumentId]); } catch { // best-effort save } @@ -518,11 +562,13 @@ [resolvedDoc.id]: resolvedDoc.initialContent, }; } + touchDocument(resolvedDoc.id); savedDocumentContent = { ...savedDocumentContent, [resolvedDoc.id]: openDocuments[resolvedDoc.id] ?? resolvedDoc.initialContent, }; + pruneDocumentCache([resolvedDoc.id]); activeDocumentId = resolvedDoc.id; activeDocumentLabel = resolvedDoc.label; } @@ -540,7 +586,10 @@ } function handleDocumentContentChange(content: string) { + if (!activeDocumentId) return; openDocuments = { ...openDocuments, [activeDocumentId]: content }; + touchDocument(activeDocumentId); + pruneDocumentCache([activeDocumentId]); } async function handleForceSave() { @@ -552,6 +601,7 @@ openDocuments = remaining; const { [id]: __, ...savedRemaining } = savedDocumentContent; savedDocumentContent = savedRemaining; + documentAccessOrder = documentAccessOrder.filter((item) => item !== id); } async function performDelete(id: string) {