Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: expand trigger on the right, connection badge as tall as it

The floating row over the terminal now spans its top edge: the state
badges (connection first, then the turn-state ones, which move over from
the top-right corner) on the left, the expand trigger on the right.
The connection badge takes the trigger's height. The row passes pointer
events through its empty middle.
This commit is contained in:
iris 2026-10-04 16:31:15 +02:00 • committed by mara
commit 51da879db6
2 changed files with 94 additions and 70 deletions

View file

@ -76,35 +76,40 @@
padding-block-start: 3.4em;
}
/* Floating turn_state/agent_state/model/ctx/cost badge cluster —
positioned by `.terminal-wrap`'s own `position: relative`, same
anchor `@hive/shared/terminal.css`'s `.tail-pill` already uses, rather
than a second positioned ancestor of our own. Top-right corner: the
scroll-to-bottom `.tail-pill` (that same stylesheet) anchors
bottom-right, so the two overlays never compete for the same corner.
`flex-wrap` rather than a fixed width — a badge cluster this narrow
can still overflow the box at small viewport widths, and wrapping
beats clipping or shrinking the terminal underneath. */
.ui-agent-term-preview-header-badges {
position: absolute;
top: 0.4em;
right: 0.4em;
z-index: 1;
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 0.25em;
max-width: calc(100% - 0.8em);
}
/* Connection badge and expand trigger — top-left floating twin of
`.ui-agent-term-preview-header-badges` (top-right), same anchor. */
/* 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
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
this edge. The row itself takes no pointer events, so the terminal
under its empty middle still scrolls and selects; only the badges and
the trigger do. `flex-wrap` on the badges rather than a fixed width —
at small viewport widths wrapping beats clipping. */
.ui-agent-term-preview-controls {
position: absolute;
top: 0.4em;
left: 0.4em;
right: 0.4em;
z-index: 1;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.5em;
pointer-events: none;
}
.ui-agent-term-preview-controls > * {
pointer-events: auto;
}
.ui-agent-term-preview-header-badges {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.25em;
min-width: 0;
}
/* Same height as the expand trigger beside it, which keeps an interactive
badge's 2.75em touch-target floor. */
.ui-agent-term-preview-live {
min-height: 2.75em;
}

View file

@ -130,6 +130,60 @@ 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({
header,
ctx,
cost,
}: {
header: NonNullable<ReturnType<typeof useSwarmAgentStateStream>["header"]>;
ctx: number | null;
cost: number | null;
}) {
return (
<>
<Badge
tone={TURN_STATE_TONE[header.turn_state] ?? "neutral"}
value={header.turn_state}
/>
{/* Only the non-boring case gets a badge — a healthy running
agent (`agent_state: "up"`) doesn't need one for the
obvious default. In practice this only ever fires for
"paused": `hive-agent` can only honestly publish "up" or
"paused" on this subject (see `AgentStateMsg::agent_state`'s
own doc), never "offline"/"destroyed" — a stopped or
destroyed agent publishes nothing at all. */}
{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
}
/>
{ctx !== null && (
<Badge
label="ctx"
value={fmtTokens(ctx)}
title="tokens used in the current context window"
/>
)}
{cost !== null && (
<Badge
label="cost"
value={fmtTokens(cost)}
title="cumulative tokens billed across the last turn"
/>
)}
</>
);
}
export function AgentTermPreview({
agentName,
subagent,
@ -176,15 +230,21 @@ export function AgentTermPreview({
}
>
<div class="terminal-wrap ui-agent-term-preview-wrap">
{/* The connection badge and the expand trigger float in the
terminal's top-left corner (the other badges own the
top-right). The full-tab mount is already the destination, so
{/* One floating row over the terminal's top edge: the state
badges (connection first) on the left, the expand trigger on
the right. The full-tab mount is already the destination, so
it has no expand trigger. */}
<div class="ui-agent-term-preview-controls">
<Badge
tone={CONNECTION_TONE[connection]}
value={CONNECTION_LABEL[connection]}
/>
<div class="ui-agent-term-preview-header-badges">
<Badge
class="ui-agent-term-preview-live"
tone={CONNECTION_TONE[connection]}
value={CONNECTION_LABEL[connection]}
/>
{header && showHeaderBadges && (
<StateBadges header={header} ctx={ctx} cost={cost} />
)}
</div>
{!fullHeight && (
<Badge
icon={<ExpandIcon />}
@ -194,47 +254,6 @@ export function AgentTermPreview({
/>
)}
</div>
{header && showHeaderBadges && (
<div class="ui-agent-term-preview-header-badges">
<Badge
tone={TURN_STATE_TONE[header.turn_state] ?? "neutral"}
value={header.turn_state}
/>
{/* Only the non-boring case gets a badge — a healthy running
agent (`agent_state: "up"`) doesn't need one for the
obvious default. In practice this only ever fires for
"paused": `hive-agent` can only honestly publish "up" or
"paused" on this subject (see `AgentStateMsg::agent_state`'s
own doc), never "offline"/"destroyed" — a stopped or
destroyed agent publishes nothing at all. */}
{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
}
/>
{ctx !== null && (
<Badge
label="ctx"
value={fmtTokens(ctx)}
title="tokens used in the current context window"
/>
)}
{cost !== null && (
<Badge
label="cost"
value={fmtTokens(cost)}
title="cumulative tokens billed across the last turn"
/>
)}
</div>
)}
<div class="live terminal" ref={logRef} onScroll={handleScroll}>
{rows.length === 0 ? (
<div class="row note">