From e13762fe9a94b113bc0074fbac857425c2804cb9 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Mon, 14 Sep 2026 10:06:01 +0300 Subject: [PATCH] Stats bar round 2 (owner): popover centered over the summary button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 1 centered the popover over the whole bar ROW — the owner meant the stats bar's own bottom-left content (the context · cost summary button). The popover now anchors to a relative wrapper around the button and centers over IT (left-1/2 -translate-x-1/2 of the wrapper), so it hangs balanced over the trigger, not offset toward the composer row's center. Outside-click + Esc closing unchanged from round 1. --- components/stats-bar.tsx | 144 ++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 70 deletions(-) diff --git a/components/stats-bar.tsx b/components/stats-bar.tsx index 819416a..0998fa6 100644 --- a/components/stats-bar.tsx +++ b/components/stats-bar.tsx @@ -14,8 +14,8 @@ import { ModelControl } from "@/components/model-picker"; * All folds come from core/prices.ts (sessionCost, contextWindow, turnCost) * — no cost math lives here. Absent data renders "—", never zero (the * doctrine): no turns yet → every figure is "—"; turns the price table - * can't price count as unpriced, shown in the popover, never folded into a - * fake $0. The RIGHT side is the model + thinking control (components/ + * can't price count as unpriced, shown in the popover, never folded into + * a fake $0. The RIGHT side is the model + thinking control (components/ * model-picker.tsx): one controller for the conversation agents, backed by * pi-ai's catalog; the buttons show the EFFECTIVE model (the record's * settings, else config's pin), while the left-side figures still fold the @@ -61,28 +61,84 @@ export function StatsBar({ return (
- + {/* The summary button + its popover: the popover anchors to this + wrapper, CENTERED over the button itself (owner, stats round 2) + — not over the whole bar row, which made it line up oddly. */} +
+ + {open && ( +
+
+ Session stats +
+
+
+ Context (last call) + + {ctx ? `${formatTokens(ctx.usedTokens)} / ${formatTokens(ctx.limit)}` : "—"} + +
+
+ Total cost + {costLabel} +
+ {unpriced > 0 && ( +
+ Unpriced turns + {unpriced} +
+ )} +
+ {turns.length > 0 && ( + <> +
+ Turns +
+
    + {turns.map((turn, i) => { + const c = turnCost(turn); + return ( +
  • + + #{i + 1} · {prettyModel(turn.model)} + + + {turn.steps.length} steps + {c === null ? "—" : formatUsd(c)} + +
  • + ); + })} +
+ + )} +
+ )} +
{/* The model + thinking control — one controller for the conversation agents (prober/teacher); the record's `turns` above still folds the ACTUAL models billed, regardless of what the picker says. */} @@ -92,58 +148,6 @@ export function StatsBar({ onSaved={onSettingsSaved} fallbackModelId={lastModel} /> - - {open && ( -
-
- Session stats -
-
-
- Context (last call) - - {ctx ? `${formatTokens(ctx.usedTokens)} / ${formatTokens(ctx.limit)}` : "—"} - -
-
- Total cost - {costLabel} -
- {unpriced > 0 && ( -
- Unpriced turns - {unpriced} -
- )} -
- {turns.length > 0 && ( - <> -
- Turns -
-
    - {turns.map((turn, i) => { - const c = turnCost(turn); - return ( -
  • - - #{i + 1} · {prettyModel(turn.model)} - - - {turn.steps.length} steps - {c === null ? "—" : formatUsd(c)} - -
  • - ); - })} -
- - )} -
- )}
); -} \ No newline at end of file +}