From 80f8a219c89a963c485da0f40dc428bf688fedb7 Mon Sep 17 00:00:00 2001
From: Adam Malczewski <github@tradam.dev>
Date: Sun, 7 Jun 2026 18:52:13 +0900
Subject: Revert "feat(chat): live turn metrics — telemetry reducer +
 rendering"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This reverts commit 48c6d85c3cc5a57a729f14068e2346b17ed62088.
---
 src/features/chat/ui/ChatView.svelte | 93 ++++++++++--------------------------
 1 file changed, 25 insertions(+), 68 deletions(-)

(limited to 'src/features/chat/ui/ChatView.svelte')

diff --git a/src/features/chat/ui/ChatView.svelte b/src/features/chat/ui/ChatView.svelte
index 6acda53..3a078fb 100644
--- a/src/features/chat/ui/ChatView.svelte
+++ b/src/features/chat/ui/ChatView.svelte
@@ -1,27 +1,16 @@
 <script lang="ts">
 	import { groupRenderedChunks, type RenderedChunk } from "../index";
-	import type { TelemetryState } from "../../../core/telemetry";
-	import { stepMetrics, stepTps } from "../../../core/telemetry";
 
-	interface Props {
-		chunks: readonly RenderedChunk[];
-		telemetry: TelemetryState;
-		currentTurnId: string | null;
-	}
-
-	let { chunks, telemetry, currentTurnId }: Props = $props();
+	let { chunks }: { chunks: readonly RenderedChunk[] } = $props();
 
 	const groups = $derived(groupRenderedChunks(chunks));
 
-	function formatMs(ms: number): string {
-		if (ms < 1000) return `${Math.round(ms)}ms`;
-		const s = ms / 1000;
-		return s < 60 ? `${s.toFixed(1)}s` : `${Math.floor(s / 60)}m${Math.round(s % 60)}s`;
-	}
-
+	// Stable per-row keys. Thinking blocks get an ordinal key (`think<n>`) that
+	// survives the provisional→committed (seq null → seq N) transition, so the
+	// collapse's open/close state is NOT lost when a turn seals. (App isolates
+	// these keys per conversation via {#key}.)
 	const rows = $derived.by(() => {
 		let thinking = 0;
-		let stepIdx = 0;
 		return groups.map((group, i) => {
 			let key: string;
 			if (group.kind === "tool-batch") {
@@ -33,17 +22,14 @@
 			} else {
 				key = `p${i}`;
 			}
-			const si = stepIdx;
-			if (group.kind === "tool-batch" || (group.kind === "single" && (group.chunk.chunk.type === "tool-call" || group.chunk.chunk.type === "tool-result"))) {
-				stepIdx++;
-			}
-			return { group, key, stepIdx: si };
+			return { group, key };
 		});
 	});
 </script>
 
-{#snippet chunkRow(rendered: RenderedChunk, sIdx: number)}
+{#snippet chunkRow(rendered: RenderedChunk)}
 	{#if rendered.role === "user"}
+		<!-- User: a speech bubble, left-aligned -->
 		<div class="chat chat-start">
 			<div class="chat-bubble chat-bubble-primary">
 				{#if rendered.chunk.type === "text"}
@@ -52,6 +38,9 @@
 			</div>
 		</div>
 	{:else if rendered.chunk.type === "thinking"}
+		<!-- Thinking: a visible bubble (like tool cards), holding a checkbox collapse
+		     (no arrow icon, smooth open/close). Title reads "Thinking" + loading dots
+		     while generating, then "Thoughts" with no dots once complete. -->
 		<div class="chat chat-start [&>.chat-bubble]:max-w-5xl [&>.chat-bubble]:p-0">
 			<div class="chat-bubble w-full bg-transparent">
 				<div class="collapse w-full rounded-box bg-base-200 text-sm">
@@ -69,18 +58,14 @@
 			</div>
 		</div>
 	{:else if rendered.chunk.type === "tool-call" || rendered.chunk.type === "tool-result"}
-		{@const step = currentTurnId ? stepMetrics(telemetry, currentTurnId, sIdx) : undefined}
-		{@const toolDur = step?.toolDurationMs}
+		<!-- Single tool call/result: a regular (non-speech) card. Nested in the
+		     chat-start grid via a transparent, padding-stripped chat-bubble shim so
+		     the card inherits the same left offset as the bubble bodies. -->
 		<div class="chat chat-start [&>.chat-bubble]:max-w-full [&>.chat-bubble]:p-0">
 			<div class="chat-bubble bg-transparent">
 				{#if rendered.chunk.type === "tool-call"}
 					<div class="w-fit max-w-full rounded-box bg-base-200 p-3 text-sm">
-						<div class="flex items-center gap-2">
-							<strong>{rendered.chunk.toolName}</strong>
-							{#if toolDur !== undefined && toolDur > 0}
-								<span class="badge badge-ghost badge-xs ml-auto">{formatMs(toolDur)}</span>
-							{/if}
-						</div>
+						<strong>{rendered.chunk.toolName}</strong>
 						<pre class="text-xs mt-1">{JSON.stringify(rendered.chunk.input, null, 2)}</pre>
 					</div>
 				{:else}
@@ -88,43 +73,19 @@
 						class="w-fit max-w-full rounded-box bg-base-200 p-3 text-sm"
 						class:text-error={rendered.chunk.isError}
 					>
-						<div class="flex items-center gap-2">
-							<strong>{rendered.chunk.toolName}</strong>
-							{#if toolDur !== undefined && toolDur > 0}
-								<span class="badge badge-ghost badge-xs ml-auto">{formatMs(toolDur)}</span>
-							{/if}
-						</div>
+						<strong>{rendered.chunk.toolName}</strong>
 						<pre class="text-xs mt-1">{rendered.chunk.content}</pre>
 					</div>
 				{/if}
 			</div>
 		</div>
 	{:else}
-		{@const step = currentTurnId ? stepMetrics(telemetry, currentTurnId, sIdx) : undefined}
-		{@const tps = step ? stepTps(step) : undefined}
+		<!-- Assistant text / system / error: an INVISIBLE speech bubble — same
+		     chat-start grid as the user bubble, so it inherits identical left spacing. -->
 		<div class="chat chat-start [&>.chat-bubble]:max-w-5xl">
 			<div class="chat-bubble w-full bg-transparent">
 				{#if rendered.chunk.type === "text"}
-					<ul class="list rounded-box text-sm">
-						<li class="list-row">
-							<p>{rendered.chunk.text}</p>
-						</li>
-						{#if step && (step.genTotalMs !== undefined || tps !== undefined || step.usage?.outputTokens !== undefined)}
-							<li class="list-row">
-								{#if step.genTotalMs !== undefined}
-									<span class="badge badge-ghost badge-xs">{formatMs(step.genTotalMs)}</span>
-								{/if}
-								<span>·</span>
-								{#if tps !== undefined}
-									<span class="badge badge-ghost badge-xs">{Math.round(tps)} t/s</span>
-								{/if}
-								<span>·</span>
-								{#if step.usage?.outputTokens !== undefined}
-									<span class="badge badge-ghost badge-xs">{step.usage.outputTokens} tok</span>
-								{/if}
-							</li>
-						{/if}
-					</ul>
+					<p>{rendered.chunk.text}</p>
 				{:else if rendered.chunk.type === "error"}
 					<div class="text-error" role="alert">
 						{rendered.chunk.message}
@@ -141,24 +102,20 @@
 {/snippet}
 
 <div class="flex flex-col gap-2 p-4 pl-6" role="log" aria-live="polite">
-	{#each rows as { group, key, stepIdx } (key)}
+	{#each rows as { group, key } (key)}
 		{#if group.kind === "single"}
-			{@render chunkRow(group.chunk, stepIdx)}
+			{@render chunkRow(group.chunk)}
 		{:else}
-			{@const step = currentTurnId ? stepMetrics(telemetry, currentTurnId, stepIdx) : undefined}
-			{@const toolDur = step?.toolDurationMs}
+			<!-- Batched tool calls (one step): a single bubble holding a DaisyUI list,
+			     one row per call paired with its result. Same chat-start grid shim as
+			     the single tool card so it lines up with the other messages. -->
 			<div class="chat chat-start [&>.chat-bubble]:max-w-full [&>.chat-bubble]:p-0">
 				<div class="chat-bubble bg-transparent">
 					<ul class="list w-fit max-w-full rounded-box bg-base-200 text-sm">
 						{#each group.entries as entry (entry.call.toolCallId)}
 							<li class="list-row">
 								<div>
-									<div class="flex items-center gap-2">
-										<strong>{entry.call.toolName}</strong>
-										{#if toolDur !== undefined && toolDur > 0}
-											<span class="badge badge-ghost badge-xs ml-auto">{formatMs(toolDur)}</span>
-										{/if}
-									</div>
+									<strong>{entry.call.toolName}</strong>
 									<pre class="text-xs mt-1">{JSON.stringify(entry.call.input, null, 2)}</pre>
 									{#if entry.result}
 										<pre
-- 
cgit v1.2.3

