From 3420b4eb54f2f06085e43a0407bdb505a4bc1153 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 14 Apr 2026 14:05:05 +1000 Subject: [PATCH 1/2] fix(staged): memoize groupByVerb to prevent UI freeze on large sessions groupByVerb() was called directly in the Svelte template expression, causing it to re-run on every render. For sessions with hundreds of tool calls, this meant hundreds of JSON.parse + string replace operations on every reactivity update, freezing the main thread. Pre-compute verb groups for all tool groups in a $derived block, caching both past-tense and present-tense variants. The template now does a cheap boolean lookup instead of re-running the expensive computation. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../lib/features/sessions/SessionModal.svelte | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/staged/src/lib/features/sessions/SessionModal.svelte b/apps/staged/src/lib/features/sessions/SessionModal.svelte index fa6160df8..a66945852 100644 --- a/apps/staged/src/lib/features/sessions/SessionModal.svelte +++ b/apps/staged/src/lib/features/sessions/SessionModal.svelte @@ -75,6 +75,7 @@ hasXmlBlocks, stripCodeFences, stripXmlTags, + type VerbGroup, } from './sessionModalHelpers'; import InContentSearch from '../../shared/InContentSearch.svelte'; import { highlightMatches, clearHighlights, scrollToMatch } from '../../shared/textHighlight'; @@ -926,6 +927,30 @@ return arr; }); + /** + * Pre-compute verb groups for every tools group in both tenses. + * `groupByVerb` is expensive (JSON.parse + string replace per tool call) so we + * cache both the past-tense and present-tense variants here. The template then + * picks the right variant with a cheap boolean lookup instead of re-running the + * heavy computation on every render. + */ + let verbGroupCache = $derived.by(() => { + const cache: { past: VerbGroup[]; present: VerbGroup[] }[] = []; + for (const group of grouped) { + if (group.type === 'tools') { + cache.push({ + past: groupByVerb(group.pairs, repoDir, true), + present: groupByVerb(group.pairs, repoDir, false), + }); + } else { + // Placeholder — tool-group index won't line up otherwise. + // We use a separate counter in the template instead. + cache.push({ past: [], present: [] }); + } + } + return cache; + }); + /** Stable key for a message group — used to key the {#each} block for transitions. * For tools groups, keys off the first pair — safe because the grouping logic * in `grouped` always pushes at least one pair before creating a tools group. */ @@ -1124,8 +1149,9 @@ {:else} + {@const forcePastTense = !isLive || sending || hasUserAfter[groupIdx]}
- {#each groupByVerb(group.pairs, repoDir, !isLive || sending || hasUserAfter[groupIdx]) as vg, vgIdx} + {#each (forcePastTense ? verbGroupCache[groupIdx].past : verbGroupCache[groupIdx].present) as vg, vgIdx} {#if vg.items.length === 1} {@const item = vg.items[0]} {@const isExpanded = expandedTools.has(item.pair.call.id)} From 2773f38991dd64b8f14b0f2ebcb7e987878bd368 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 14 Apr 2026 14:56:18 +1000 Subject: [PATCH 2/2] style: fix prettier formatting Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/differ/src-tauri/Cargo.toml | 2 ++ apps/staged/src-tauri/Cargo.toml | 2 ++ apps/staged/src/lib/features/sessions/SessionModal.svelte | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/differ/src-tauri/Cargo.toml b/apps/differ/src-tauri/Cargo.toml index bd1e5ade3..2d9e86c1f 100644 --- a/apps/differ/src-tauri/Cargo.toml +++ b/apps/differ/src-tauri/Cargo.toml @@ -28,3 +28,5 @@ git-diff = { path = "../../../crates/git-diff" } # macOS font enumeration [target.'cfg(target_os = "macos")'.dependencies] core-text = "20" + +[workspace] diff --git a/apps/staged/src-tauri/Cargo.toml b/apps/staged/src-tauri/Cargo.toml index 0bb91c178..5284e0072 100644 --- a/apps/staged/src-tauri/Cargo.toml +++ b/apps/staged/src-tauri/Cargo.toml @@ -66,3 +66,5 @@ axum = { version = "0.8" } # [[bin]] # name = "debug_diff" # path = "src/bin/debug_diff.rs" + +[workspace] diff --git a/apps/staged/src/lib/features/sessions/SessionModal.svelte b/apps/staged/src/lib/features/sessions/SessionModal.svelte index a66945852..ed73069e4 100644 --- a/apps/staged/src/lib/features/sessions/SessionModal.svelte +++ b/apps/staged/src/lib/features/sessions/SessionModal.svelte @@ -1151,7 +1151,7 @@ {:else} {@const forcePastTense = !isLive || sending || hasUserAfter[groupIdx]}
- {#each (forcePastTense ? verbGroupCache[groupIdx].past : verbGroupCache[groupIdx].present) as vg, vgIdx} + {#each forcePastTense ? verbGroupCache[groupIdx].past : verbGroupCache[groupIdx].present as vg, vgIdx} {#if vg.items.length === 1} {@const item = vg.items[0]} {@const isExpanded = expandedTools.has(item.pair.call.id)}