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:
parent
b23f3d1ecb
commit
d951c360ae
12 changed files with 235 additions and 612 deletions
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue