diff --git a/src/dashboard.ts b/src/dashboard.ts index a412298..3506095 100644 --- a/src/dashboard.ts +++ b/src/dashboard.ts @@ -150,21 +150,16 @@ export interface RecentRow { */ interface SessionTotals { sessionId: string; - firstSeen: number; // unix seconds - lastSeen: number; // unix seconds - requests: number; // Dollar-weighted accumulators. baseline/actual are the SAME math as the - // global `Totals.allBaselineEquivalentWeighted` / `allActualInputWeighted` - // + `allOutputWeighted`, but scoped to a single session. + // global `Totals.allBaselineEquivalentWeighted` / `allActualInputWeighted`, + // but scoped to a single session. Output is excluded because the proxy + // doesn't touch it. baselineInputWeighted: number; actualInputWeighted: number; - outputWeighted: number; - // Per-bucket char attribution from ev.info.bucketChars. - bucketChars: { [bucket: string]: number }; - // Passthrough reason histogram from ev.info.passthroughReasons. - passthroughReasons: Record; - passthroughRequests: number; // count of requests where !ev.info.compressed - compressedRequests: number; + // Honest denominator for the headline: count of requests that contributed + // to `baselineInputWeighted` (events that carried a baseline measurement, + // matching the global `Totals.baselineMeasuredCount`). + baselineMeasuredCount: number; } interface Totals { @@ -512,59 +507,30 @@ export class DashboardState { if (!s) { s = { sessionId: sid, - firstSeen: Date.now() / 1000, - lastSeen: Date.now() / 1000, - requests: 0, baselineInputWeighted: 0, actualInputWeighted: 0, - outputWeighted: 0, - bucketChars: {}, - passthroughReasons: {}, - passthroughRequests: 0, - compressedRequests: 0, + baselineMeasuredCount: 0, }; this.sessions.set(sid, s); - // Cap memory — drop the oldest session by lastSeen when over budget. + // Cap memory — drop the first (oldest by insertion order) session + // when over budget. We no longer track lastSeen privately on the + // class — insertion order is a fine proxy because `currentSessionId` + // (set above on every update) is what the serve path uses to pick + // the most-recent session, not a scan of `this.sessions`. if (this.sessions.size > DashboardState.SESSION_CAP) { - const oldest = [...this.sessions.values()].sort( - (a, b) => a.lastSeen - b.lastSeen, - )[0]; - if (oldest) this.sessions.delete(oldest.sessionId); + const firstKey = this.sessions.keys().next().value; + if (firstKey !== undefined) this.sessions.delete(firstKey); } } - s.lastSeen = Date.now() / 1000; - s.requests += 1; // Reuse the same haveUsage / haveBaseline guards + the - // baselineInputEff / actualInputEff / outputEquiv locals computed - // earlier in update() so the lifetime totals block (above) and the - // per-session block (here) read the same values. Re-deriving them - // here would duplicate the cache-aware-baseline math and invite drift. + // baselineInputEff / actualInputEff locals computed earlier in + // update() so the lifetime totals block (above) and the per-session + // block (here) read the same values. Re-deriving them here would + // duplicate the cache-aware-baseline math and invite drift. if (haveBaseline && haveUsage) { s.baselineInputWeighted += baselineInputEff; s.actualInputWeighted += actualInputEff; - s.outputWeighted += outputEquiv; - } - if (compressed) s.compressedRequests += 1; - else s.passthroughRequests += 1; - // Per-bucket char attribution from `info.bucketChars` (flat - // `Partial>` — see src/core/tracker.ts). - // Matches the shape the Svelte panel iterates over. - const bc = info?.bucketChars; - if (bc && typeof bc === 'object') { - for (const [key, val] of Object.entries(bc) as [string, unknown][]) { - if (typeof val === 'number' && Number.isFinite(val)) { - s.bucketChars[key] = (s.bucketChars[key] ?? 0) + val; - } - } - } - // Passthrough reason histogram from `info.passthroughReasons`. - const pr = info?.passthroughReasons; - if (pr && typeof pr === 'object') { - for (const [reason, count] of Object.entries(pr as Record)) { - if (typeof count === 'number' && Number.isFinite(count) && count > 0) { - s.passthroughReasons[reason] = (s.passthroughReasons[reason] ?? 0) + count; - } - } + s.baselineMeasuredCount += 1; } } @@ -695,28 +661,16 @@ export class DashboardState { if (!s) { return jsonResponse({ sessionId: null, message: 'no active session yet' }); } - // Dollar-weighted savings ratio. Same math as the global `saved_pct` - // (compressed AND passthrough requests — passthrough has baseline=actual, - // so it correctly drags the ratio down when we leak). Honest denominator: - // includes both compressed and uncompressed flows that the proxy saw. - const baselineUsd = (s.baselineInputWeighted * ASSUMED_INPUT_USD_PER_MTOK) / 1e6; - const actualUsd = (s.actualInputWeighted * ASSUMED_INPUT_USD_PER_MTOK) / 1e6; - const savedUsd = baselineUsd - actualUsd; - const savedPct = baselineUsd > 0 ? (savedUsd / baselineUsd) * 100 : 0; + // Minimal headline payload: the same dollar-weighted baseline/actual + // input accumulators the global Totals block exposes, plus the honest + // denominator (`baselineMeasuredCount` — only requests that carried a + // baseline measurement). The Svelte panel does the savings math itself + // so we don't round-trip dollar values through the wire. return jsonResponse({ sessionId: s.sessionId, - firstSeen: s.firstSeen, - lastSeen: s.lastSeen, - uptimeSec: Math.max(0, s.lastSeen - s.firstSeen), - requests: s.requests, - compressedRequests: s.compressedRequests, - passthroughRequests: s.passthroughRequests, - baselineUsd: round4(baselineUsd), - actualUsd: round4(actualUsd), - savedUsd: round4(savedUsd), - savedPct: round1(savedPct), - bucketChars: s.bucketChars, - passthroughReasons: s.passthroughReasons, + baselineInputWeighted: s.baselineInputWeighted, + actualInputWeighted: s.actualInputWeighted, + baselineMeasuredCount: s.baselineMeasuredCount, }); } diff --git a/src/dashboard/components/SessionSummary.svelte b/src/dashboard/components/SessionSummary.svelte index 25713c0..1ff29d3 100644 --- a/src/dashboard/components/SessionSummary.svelte +++ b/src/dashboard/components/SessionSummary.svelte @@ -1,263 +1,66 @@ -
- {#if err} -
error: {err}
- {:else if !data} -
loading…
- {:else if !hasSession} -

This session

-
{data.message ?? 'no active session yet'}
- {:else} -
-

This session

-
- started {fmtDuration(data.uptimeSec ?? 0)} ago · {data.requests} requests -
-
- -
-
- Saved {fmtPct(data.savedPct ?? 0)} -
-
- {fmtUsd(data.savedUsd ?? 0)} of {fmtUsd(data.baselineUsd ?? 0)} baseline -
-
- -
-
-
- - {#if bucketEntries.length > 0} -
- -
- {#each bucketEntries as [key, val]} - {@const w = bucketTotal > 0 ? (val / bucketTotal) * 100 : 0} -
- {/each} -
-
- {#each bucketEntries as [key, val]} - {@const w = bucketTotal > 0 ? (val / bucketTotal) * 100 : 0} - - - {BUCKET_LABELS[key] ?? key} {w.toFixed(0)}% - - {/each} -
-
- {/if} - - {#if passthroughEntries.length > 0} -
- -
    - {#each passthroughEntries as [reason, count]} -
  • {reason} {count}
  • - {/each} -
-
- {:else if (data.passthroughRequests ?? 0) === 0} -
- -
- {/if} - {/if} -
+{#if err} +
session: {err}
+{:else if measuredReqs > 0} +
+ THIS SESSION + — saved {fmtUsd(savedUsd)} + ({savedPct.toFixed(1)}%) + · {measuredReqs} requests +
+{/if} diff --git a/src/dashboard/types.ts b/src/dashboard/types.ts index b520b1a..0852cf4 100644 --- a/src/dashboard/types.ts +++ b/src/dashboard/types.ts @@ -153,27 +153,13 @@ export interface CompressionToggleResponse { compression_enabled: boolean; } -/** /api/current-session.json payload — per-session aggregates for the most-recently-active Claude Code session. */ +/** /api/current-session.json payload — per-session aggregates for the most-recently-active Claude Code session. + * Pruned to only the fields the minimal SessionSummary.svelte headline reads: + * dollar-weighted baseline/actual input + the measured-request count. */ export interface CurrentSessionPayload { sessionId: string | null; message?: string; - firstSeen?: number; - lastSeen?: number; - uptimeSec?: number; - requests?: number; - compressedRequests?: number; - passthroughRequests?: number; - baselineUsd?: number; - actualUsd?: number; - savedUsd?: number; - savedPct?: number; - bucketChars?: { - static_slab: number; - reminder: number; - tool_result: number; - history: number; - billing: number; - dynamic: number; - }; - passthroughReasons?: Record; + baselineInputWeighted?: number; + actualInputWeighted?: number; + baselineMeasuredCount?: number; }