Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: full-tab agent terminal (#4506)

Adds a full, non-capped agent terminal reachable from a new expand
trigger on the embedded AgentTermPreview (the detail-panel preview on
AgentsPage stays as-is, just gains the trigger). Opens
/agents/:name/terminal in a new dynamic tab in Shell's header, next to
the static nav row — tabs persist across a reload via useDynamicTabs,
a small localStorage-backed hook built on @hive/shared's existing
settings-storage primitive.

AgentTermPreview gains two new props to support both mounts from one
component: fullHeight (drops the 12em preview cap, fills its page)
and showHeaderBadges (default true — lets a future caller that
already shows turn_state/model/ctx/cost elsewhere suppress this
cluster; AgentsPage doesn't use it, see below).

Deviation from the originally posted plan (issue comment 80596): that
plan proposed AgentsPage's embedded preview pass showHeaderBadges as
false, reasoning the detail panel already duplicates that info.
Checked the actual code before implementing — it doesn't; AgentRow/
AgentTypes.ts carry none of turn_state/model/ctx/cost, and
AgentTermPreview's own floating badges are the only place swarm-ui
shows them. Left the badges visible there instead of shipping a
regression the plan's own stated justification didn't hold up to.

Also fixed a same-tab pub/sub race found by actually rendering a cold
load of /agents/:name/terminal (headless chromium, not just reasoning
about the code): useLocalSetting subscribes inside a useEffect, and
mount effects fire children-before-parents, so a descendant's
mount-time write (AgentTerminalPage registering its own tab) can beat
an ancestor's (Shell's) subscription into existence, leaving Shell's
tab row silently empty on a direct/reload load. Fixed by having
useDynamicTabs re-sync from storage on every location change, not
just on notify() — the fix lives in the new hook itself, not in the
shared settings-storage primitive theme/motion overrides also use.
This commit is contained in:
iris 2026-09-28 21:43:29 +02:00 • committed by mara
commit c460e91bd1
9 changed files with 445 additions and 11 deletions

View file

@ -8,14 +8,60 @@
margin-block-start: 1rem;
}
.ui-agent-term-preview-status {
/* Holds the connection-status badge + (unless `fullHeight`) the
expand-to-fullscreen trigger — `margin-left: auto` targets the expand
trigger by its OWN class, not `:last-child` (tried that first; it
silently broke when `fullHeight` hides the expand trigger, since the
status badge then becomes the `:last-child` too and gets pushed to
the right edge instead of staying flush left — caught by actually
rendering the `fullHeight` case, not just reasoning about the CSS).
Same split-row intent `Shell.css`'s `.shell-header-actions` already
uses for its own pair of triggers, just addressed directly instead of
positionally. */
.ui-agent-term-preview-toolbar {
display: flex;
align-items: center;
margin-block-end: 0.5rem;
}
.ui-agent-term-preview-expand {
margin-left: auto;
}
.ui-agent-term-preview-wrap.terminal-wrap {
max-height: 12em;
}
/* `AgentTerminalPage`'s full-tab mount (`fullHeight` prop → the
`-full` modifier on the OUTER `.ui-agent-term-preview` root, not the
wrap) — self-contained flex chain, not dependent on the page around
it doing anything beyond giving this root a real height to fill (see
AgentTerminalPage.css). Column flex so the toolbar keeps its natural
height and the terminal-wrap below it takes the rest; `flex: 1` +
`min-height: 0` on the wrap (not `height: 100%`) is deliberate — a
flex item's `height` percentage doesn't account for a sibling
(the toolbar) already consuming space the way `flex: 1` does, and
`min-height: 0` is what actually lets the item shrink below its
content's natural height so `.live.terminal`'s own `overflow-y: auto`
(terminal.css) scrolls instead of the whole column growing past its
container. `!important`-free, but the `max-height: none` overrides
below rely on source order (equal specificity to the un-modified
rules above — same class count on both sides), not on being "more
specific" — keep this block after the un-modified one if this file
is ever reordered. */
.ui-agent-term-preview-full {
display: flex;
flex-direction: column;
}
.ui-agent-term-preview-full .ui-agent-term-preview-wrap.terminal-wrap {
max-height: none;
flex: 1;
min-height: 0;
}
.ui-agent-term-preview-full .ui-agent-term-preview-wrap .live.terminal {
max-height: none;
height: 100%;
}
/* `box-sizing: border-box`, not the browser's default `content-box` —
`.live.terminal`'s own `padding: 0.8em 1em 0.4em` (terminal.css) is
otherwise added ON TOP of this `max-height`, so the box renders