summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/ui/CacheTimerBadge.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-10-19 17:16:48 +0900
committerAdam Malczewski <[email protected]>2026-10-19 17:16:48 +0900
commit6122a90cf0c9f938945e684e19e74153b38e7b4b (patch)
treee97523e61581de17b7cb8705f2913b4913c28aeb /src/features/chat/ui/CacheTimerBadge.svelte
parent01f9fb2a4ad6110def531ead16b370c818e8ee81 (diff)
downloaddispatch-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.svelte24
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
>