// — the agent page's status row: the consolidated status // badge (alive/thinking/paused/etc., with pause/resume + cancel-turn in // its dropdown), the model + effort pickers, and context/cost usage. // This is the concrete fix for the design guide's own named anti-example // (the old page's model/effort *pickers* lived in the `⋯` overflow menu // while the current model/effort only showed as a disconnected chip) — // every badge here IS the control for what it displays, composed from // `@hive/shared`'s `Badge`/`Dropdown` (../../shared/src/badge, // ../../shared/src/dropdown). Presentational only: formatting + // selection state live in the caller (`Root.tsx`, via the // `useAgentState` hook), so this component can still be demoed and // reviewed against plain sample data independent of live `/api/state`. import { useRef, useState } from "preact/hooks"; import { Badge, type BadgeTone } from "@hive/shared/badge.js"; import { Dropdown, type DropdownOption } from "@hive/shared/dropdown.js"; import "./StatusChips.css"; export interface StatusChipsProps { statusLabel: string; statusTone: BadgeTone; statusTooltip?: string; model: string; /** Concrete model id the last completed turn actually ran on, e.g. * "claude-sonnet-4-5-20260805" — shown as the badge's tooltip so the * operator can tell what an alias resolved to. `undefined` before any * turn has completed. */ resolvedModel?: string; availableModels: string[]; onSelectModel: (name: string) => void; effort: string; availableEfforts: string[]; onSelectEffort: (level: string) => void; ctxLabel?: string; costLabel?: string; paused: boolean; onTogglePause: () => void; lastTurnLabel?: string; thinking: boolean; onCancelTurn: () => Promise; } const MODEL_DESCRIPTIONS: Record = { haiku: "fast", sonnet: "balanced", opus: "powerful", }; const EFFORT_DESCRIPTIONS: Record = { low: "", medium: "default", high: "", xhigh: "", max: "", }; function Picker({ label, value, options, descriptions, onSelect, title, }: { label: string; value: string; options: string[]; descriptions: Record; onSelect: (v: string) => void; title?: string; }) { const [open, setOpen] = useState(false); const anchorRef = useRef(null); const dropdownOptions: DropdownOption[] = options.map((name) => ({ value: name, label: name, description: descriptions[name] || undefined, })); return (
setOpen((o) => !o)} expanded={open} title={title} /> { onSelect(v); setOpen(false); }} onClose={() => setOpen(false)} anchorRef={anchorRef} />
); } // The consolidated alive/thinking/paused badge (mara: "why do we // have separate alive badge? ... it could be one badge that shows // thinking / idle / paused / ... with a dropdown that opens when // clicked to pause/unpause or cancel turn"). Cancel-turn is a // click-again-to-confirm row rather than a native confirm dialog (mara: // "make the menu entry a 'click again to confirm'") — armed state resets // whenever the dropdown closes, so a stray reopen never fires it early. function StatusMenu({ statusLabel, statusTone, statusTooltip, paused, onTogglePause, thinking, onCancelTurn, }: Pick< StatusChipsProps, | "statusLabel" | "statusTone" | "statusTooltip" | "paused" | "onTogglePause" | "thinking" | "onCancelTurn" >) { const [open, setOpen] = useState(false); const [confirmCancel, setConfirmCancel] = useState(false); const anchorRef = useRef(null); function close() { setOpen(false); setConfirmCancel(false); } const options: DropdownOption[] = [ { value: "toggle-pause", label: paused ? "▶ resume" : "⏸ pause" }, ]; if (thinking) { options.push({ value: "cancel-turn", label: confirmCancel ? "■ click again to confirm" : "■ cancel turn", danger: true, }); } return (
setOpen((o) => !o)} expanded={open} /> { if (value === "toggle-pause") { onTogglePause(); close(); return; } // value === 'cancel-turn': first click arms it, second fires. if (!confirmCancel) { setConfirmCancel(true); return; } onCancelTurn(); close(); }} onClose={close} anchorRef={anchorRef} />
); } export function StatusChips({ statusLabel, statusTone, statusTooltip, model, resolvedModel, availableModels, onSelectModel, effort, availableEfforts, onSelectEffort, ctxLabel, costLabel, paused, onTogglePause, lastTurnLabel, thinking, onCancelTurn, }: StatusChipsProps) { return (
{availableEfforts.length ? ( ) : null} {ctxLabel ? ( ) : null} {costLabel ? ( ) : null} {lastTurnLabel ? ( {lastTurnLabel} ) : null}
); }