Replace token-usage panel with per-pane context-fill indicator

For a subscription user, lifetime token totals + a $ estimate aren't
actionable; how full each session's context window is right now is. So:

- Removed the UsagePanel, the titlebar 💰 chip, and Ctrl+Shift+U.
- Repurposed the transcript reader (src-tauri/src/usage.rs): get_pane_context
  returns each recent session's CURRENT context occupancy = the last
  assistant turn's input + cache_read + cache_creation tokens (the prompt
  size), instead of lifetime sums. Same UNC/$HOME/cache/recency machinery.
- src/lib/usage.ts now holds context helpers (window inference 200k vs 1M by
  whether occupancy already exceeds 200k, % , green→amber→red ramp, label).
- App polls get_pane_context (15s, visibility-gated) into a cwd→context map
  exposed via orchestration; each LeafPane looks itself up by leaf.cwd and
  renders a slim fill bar + % in its header (hidden for non-claude/unmatched
  panes).

Also fixes the narrow-pane toolbar: a ResizeObserver sets leaf--narrow /
leaf--xnarrow width tiers; the label shrinks first, split buttons / status /
secondary chips drop out by tier, and the close × + context indicator stay
pinned right and visible down to the 180px min width.

tsc clean (apart from the not-yet-installed xterm addons). Rust builds on
the Windows host; needs runtime verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-28 22:43:06 +01:00
parent b23f3d1ecb
commit d951c360ae
12 changed files with 235 additions and 612 deletions

View file

@ -10,6 +10,12 @@ import {
import { createPortal } from "react-dom";
import { type LeafNode, resolveFontSize, type LeafShellSpec } from "./tree";
import { useOrchestration } from "./orchestration";
import {
contextLabel,
contextPercent,
contextColor,
contextFraction,
} from "../../lib/usage";
import XtermPane from "../../components/XtermPane";
import type { SpawnSpec } from "../../ipc";
import "./LeafPane.css";
@ -42,6 +48,7 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
const [editingLabel, setEditingLabel] = useState(false);
const [labelDraft, setLabelDraft] = useState("");
const labelInputRef = useRef<HTMLInputElement | null>(null);
const rootRef = useRef<HTMLDivElement | null>(null);
const startEditLabel = useCallback(
(e: MouseEvent) => {
@ -156,6 +163,22 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
return () => orch.reportLeafIdle(leaf.id, false);
}, [leaf.id, orch.reportLeafIdle]);
// ---- width tier ---------------------------------------------------------
// Drives which toolbar items collapse on a narrow pane (CSS does the hiding).
// The close button + context indicator stay visible at every tier; min pane
// width is 180px (MIN_PANE_PX), so "xnarrow" must keep those reachable.
const [widthTier, setWidthTier] = useState<"" | "narrow" | "xnarrow">("");
useEffect(() => {
const el = rootRef.current;
if (!el) return;
const ro = new ResizeObserver(() => {
const w = el.clientWidth;
setWidthTier(w < 230 ? "xnarrow" : w < 320 ? "narrow" : "");
});
ro.observe(el);
return () => ro.disconnect();
}, []);
// ---- broadcast ---------------------------------------------------------
const onTerminalInput = useCallback(
(b64: string) => {
@ -386,9 +409,13 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
};
})();
const ctx =
leaf.shellKind === "wsl" && leaf.cwd ? orch.paneContext.get(leaf.cwd) : undefined;
return (
<div
className={`leaf${isActive ? " active" : ""}${isBroadcasting ? " broadcasting" : ""}${isIdle ? " idle" : ""}${isDragSource ? " drag-source" : ""}${isDragTarget ? " drag-target" : ""}`}
ref={rootRef}
className={`leaf${isActive ? " active" : ""}${isBroadcasting ? " broadcasting" : ""}${isIdle ? " idle" : ""}${isDragSource ? " drag-source" : ""}${isDragTarget ? " drag-target" : ""}${widthTier ? ` leaf--${widthTier}` : ""}`}
role="group"
aria-label={`Terminal pane: ${leaf.label ?? leaf.distro ?? "unnamed"}`}
data-leaf-id={leaf.id}
@ -537,6 +564,24 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
<span className={`pane-status ${statusOk ? "ok" : "err"}`}>{status}</span>
)}
{ctx && (
<span
className="pane-ctx"
title={`Context: ${contextLabel(ctx)} (${ctx.model})`}
>
<span className="pane-ctx-bar">
<span
className="pane-ctx-fill"
style={{
width: `${contextPercent(ctx)}%`,
background: contextColor(contextFraction(ctx)),
}}
/>
</span>
<span className="pane-ctx-pct">{contextPercent(ctx)}%</span>
</span>
)}
<span className="pane-actions">
<button
className="pane-btn"