Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: move the terminal preview's state badges into a state row of the agent details table

The small preview in the detail panel keeps only the connection badge and
expand, and no longer follows the agent's state stream; the full terminal
tab keeps all its badges. The table gets a state row with the turn state, a
paused badge, and the context and billed tokens (not the model, which the
inference row shows). The preview's top padding drops to one badge row, and
the showHeaderBadges prop goes since the full-tab mount is now the only one
with badges.
This commit is contained in:
iris 2026-10-25 14:52:57 +01:00
commit 705160c0fd
2 changed files with 94 additions and 33 deletions

View file

@ -73,13 +73,13 @@
this: with the base padding, "thinking"/"model sonnet" sat directly
on top of the terminal's first line whenever content started right
at the top (a running preview scrolled to the newest rows almost
always does). Sized for two badge rows, since the badges wrap in the
narrow details panel; each keeps the 2.75em touch-target floor. */
padding-block-start: 7.4em;
always does). One badge row: the connection badge and the expand
trigger, each keeping the 2.75em touch-target floor. */
padding-block-start: 3.4em;
}
/* The floating row over the terminal's top edge: state badges (connection
first, then turn_state/agent_state/model/ctx/cost) on the left, the
/* The floating row over the terminal's top edge: the connection badge
(and on the full tab turn_state/agent_state/model/ctx/cost) on the left, the
expand trigger on the right. Positioned by `.terminal-wrap`'s own
`position: relative`, same anchor `@hive/shared/terminal.css`'s
`.tail-pill` uses, which anchors bottom-right and so never competes for

View file

@ -19,15 +19,11 @@
// here needs to be interactive. Sourced from `useSwarmAgentStateStream`,
// not from `AgentRow` — the roster fetch `AgentsPage` already did has no
// notion of turn_state/model/ctx/cost, only `wanted`/`snapshot.running`.
// `showHeaderBadges` (default true) exists for a caller that already
// shows this info elsewhere and wants this cluster suppressed — checked
// against the original plan for this page, which proposed exactly that
// for `AgentsPage`'s embedded usage; that page's detail panel turned out
// NOT to duplicate any of this (`AgentRow`/`AgentTypes.ts` carry none of
// turn_state/model/ctx/cost), so `AgentsPage` leaves this at its default
// rather than hiding the only place swarm-ui shows it — see the commit
// message for the full reasoning. The prop still exists because
// suppressing it is a real, reusable need in principle, just not here.
// Only the full-tab mount (`fullHeight`) shows the state badges: the small
// preview in the detail panel stays free of them (mara: "the preview is small
// and should not be encroached further with badges on top, but for the full
// screen term i want it like that"), and the panel's details table lists the
// same state in a row of its own (`AgentStateRow`).
// **Expand-to-fullscreen trigger** — opens/navigates to
// `/agents/:name/terminal` (`AgentTerminalPage`) via `useDynamicTabs`,
@ -84,7 +80,7 @@ const TURN_STATE_TONE: Record<string, BadgeTone> = {
compacting: "warning",
};
function tokenTotal(u: TokenUsage | null): number | null {
export function tokenTotal(u: TokenUsage | null): number | null {
if (!u) return null;
// Matches `Root.tsx`'s own `tokenTotal`: input + both cache buckets,
// `output_tokens` deliberately excluded (shown in a title breakdown
@ -133,14 +129,17 @@ export function termPaths(agentName: string, subagent?: string): TermPaths {
// Turn state, agent state, model, ctx and cost, as the badges beside the
// connection one. Siblings only, no wrapper: they sit in the caller's row.
function StateBadges({
export function StateBadges({
header,
ctx,
cost,
withModel = true,
}: {
header: NonNullable<ReturnType<typeof useSwarmAgentStateStream>["header"]>;
ctx: number | null;
cost: number | null;
// Off where the model is shown beside them, as in the details table.
withModel?: boolean;
}) {
return (
<>
@ -158,15 +157,17 @@ function StateBadges({
{header.agent_state !== "up" && (
<Badge tone="warning" value={header.agent_state} />
)}
<Badge
label="model"
value={header.model}
title={
header.resolved_model
? `resolved to ${header.resolved_model}`
: undefined
}
/>
{withModel && (
<Badge
label="model"
value={header.model}
title={
header.resolved_model
? `resolved to ${header.resolved_model}`
: undefined
}
/>
)}
{ctx !== null && (
<Badge
label="ctx"
@ -188,7 +189,6 @@ function StateBadges({
export function AgentTermPreview({
agentName,
subagent,
showHeaderBadges = true,
fullHeight = false,
leading,
}: {
@ -196,7 +196,8 @@ export function AgentTermPreview({
// Set: this is one of `agentName`'s subagents, output only like the
// agent's own.
subagent?: string;
showHeaderBadges?: boolean;
// The full-tab mount: fills the tab, has no expand trigger, and shows the
// agent's state badges beside the connection one.
fullHeight?: boolean;
// A badge ahead of the connection badge, for the full-tab mount's
// link back to the agent's details.
@ -204,8 +205,10 @@ export function AgentTermPreview({
}) {
const paths = termPaths(agentName, subagent);
const { rows, connection, reconnect } = useSwarmTermStream(paths.stream);
// The small preview has no state badges, so it does not follow the state
// stream at all.
const { header, reconnect: reconnectState } = useSwarmAgentStateStream(
paths.state,
fullHeight ? paths.state : null,
);
const { open: openTab } = useDynamicTabs();
const logRef = useRef<HTMLDivElement>(null);
@ -259,9 +262,7 @@ export function AgentTermPreview({
}}
/>
)}
{header && showHeaderBadges && (
<StateBadges header={header} ctx={ctx} cost={cost} />
)}
{header && <StateBadges header={header} ctx={ctx} cost={cost} />}
</div>
{!fullHeight && (
<Badge