diff options
| author | Adam Malczewski <[email protected]> | 2026-10-19 17:16:48 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-10-19 17:16:48 +0900 |
| commit | 6122a90cf0c9f938945e684e19e74153b38e7b4b (patch) | |
| tree | e97523e61581de17b7cb8705f2913b4913c28aeb /src/features/chat/ui/CacheTimerBadge.svelte | |
| parent | 01f9fb2a4ad6110def531ead16b370c818e8ee81 (diff) | |
| download | dispatch-web-6122a90cf0c9f938945e684e19e74153b38e7b4b.tar.gz dispatch-web-6122a90cf0c9f938945e684e19e74153b38e7b4b.zip | |
fix(chat): cache timer no longer stalls transcript rendering
The live CacheTimerBadge effect read the `current` state it wrote, so on a
real (advancing) clock it re-ran itself until Svelte threw
effect_update_depth_exceeded, aborting the reactive flush — turn data
rendered late. The effect now uses locals and never reads its own write.
Also: drop the timer icon (time only), and size step-line timers xs to
match the step cache badge (turn lines stay sm).
Diffstat (limited to 'src/features/chat/ui/CacheTimerBadge.svelte')
| -rw-r--r-- | src/features/chat/ui/CacheTimerBadge.svelte | 24 |
1 files changed, 18 insertions, 6 deletions
diff --git a/src/features/chat/ui/CacheTimerBadge.svelte b/src/features/chat/ui/CacheTimerBadge.svelte index e5abed5..9e6f96d 100644 --- a/src/features/chat/ui/CacheTimerBadge.svelte +++ b/src/features/chat/ui/CacheTimerBadge.svelte @@ -12,12 +12,15 @@ let { since, elapsedMs, + size = "sm", now = Date.now, }: { /** Live: epoch ms the gap started. */ since?: number; /** Stamped: the gap's final duration (wins over `since`). */ elapsedMs?: number; + /** Badge size — match the neighbouring cache badge (`xs` on step lines). */ + size?: "xs" | "sm"; /** Wall clock (injected for tests). */ now?: () => number; } = $props(); @@ -25,14 +28,21 @@ // Initial read only (the effect below keeps it current while live). let current = $state(untrack(() => now())); + // Depends ONLY on `since`/`elapsedMs`/`now`. It must never READ `current` + // (it writes it): reading its own write makes the effect re-run itself on + // every clock change → `effect_update_depth_exceeded`, which aborts the whole + // reactive flush and stalls the transcript (turn data rendering late). $effect(() => { if (elapsedMs !== undefined || since === undefined) return; const start = since; - current = now(); - if (current - start >= CACHE_TIMER_MAX_MS) return; + const clock = now; + const t0 = clock(); + current = t0; + if (t0 - start >= CACHE_TIMER_MAX_MS) return; const id = setInterval(() => { - current = now(); - if (current - start >= CACHE_TIMER_MAX_MS) clearInterval(id); + const t = clock(); + current = t; + if (t - start >= CACHE_TIMER_MAX_MS) clearInterval(id); }, 1000); return () => clearInterval(id); }); @@ -48,11 +58,13 @@ </script> <span - class="badge badge-sm gap-1 font-mono tabular-nums {badgeClass[view.level]}" + class="badge font-mono tabular-nums {badgeClass[view.level]}" + class:badge-xs={size === "xs"} + class:badge-sm={size === "sm"} class:badge-outline={!live} title={live ? "Cache timer (running): time since the last model request. The Claude prompt cache expires after 5 minutes idle." : "Cache timer (stopped): how long the cache sat idle before the next request. The Claude prompt cache expires after 5 minutes idle."} data-testid={live ? "cache-timer-live" : "cache-timer-stamp"} - ><span aria-hidden="true">⏱</span>{view.label}</span + >{view.label}</span > |
