Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: a group pane's maximize and close are icon-only badges in its top right corner

The two controls sat among the pane's header badges as a labelled badge and a
cross glyph. They are now icon badges in the top right corner, through a
`trailing` slot of the terminal preview, so the header on the left keeps the
agent, split, subagents and state badges and a narrow pane wraps less.
This commit is contained in:
iris 2026-11-05 13:33:12 +01:00
commit 4876acd02a
5 changed files with 53 additions and 15 deletions

View file

@ -169,11 +169,12 @@ a group of the two, with its own header tab at `/agents/group/<id>`. In a group,
the same drag onto a pane splits that pane, as many times as you like, and
dragging a pane's header badge onto another pane moves it there. The **split**
button in a pane's header does the same for an agent whose terminal tab is open:
**right** or **below** puts it beside the pane. **maximize** opens that
agent's own terminal in its tab, or goes to the tab when one is open already. A pane
can hold a subagent's terminal (its header reads `agent/subagent`), put there by
the **subagents** badge in the pane's header. The **×** takes the pane's
agent out of the group, and a group left with one agent goes, landing on that
**right** or **below** puts it beside the pane. A pane can hold a subagent's
terminal (its header reads `agent/subagent`), put there by the **subagents**
badge in the pane's header. Two icon buttons sit in a pane's top right corner.
**Maximize** (the arrows pointing to the corners) opens that pane's own terminal
in its tab, or goes to the tab when one is open already. **Close** (the cross)
takes the pane out of the group, and a group left with one pane goes, landing on that
agent's own terminal. Drag a divider to resize, or focus it and use the arrow
keys. Panes have no minimum size; a divider stops at 2% so a pane can't
vanish.

View file

@ -83,6 +83,27 @@ export function ExpandIcon() {
);
}
// A cross, for an icon-only close trigger (a pane's "take this out of the
// group"). Same size and stroke as `ExpandIcon`.
export function CloseIcon() {
return (
<svg
width="1.3em"
height="1.3em"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<line x1="5" y1="5" x2="19" y2="19" />
<line x1="19" y1="5" x2="5" y2="19" />
</svg>
);
}
// A funnel, the standard "filter" glyph — smaller than the other two
// (1em not 1.3em) since its first caller sits inside a table header
// cell, not a nav trigger button; scale via the caller's own

View file

@ -102,6 +102,12 @@
.ui-agent-term-preview-controls > * {
pointer-events: auto;
}
/* The icon-only controls in the top right corner. */
.ui-agent-term-preview-trailing {
display: flex;
flex: none;
gap: 0.25em;
}
.ui-agent-term-preview-header-badges {
display: flex;
flex-wrap: wrap;

View file

@ -206,6 +206,7 @@ export function AgentTermPreview({
subagent,
fullHeight = false,
leading,
trailing,
}: {
agentName: string;
// Set: this is one of `agentName`'s subagents, output only like the
@ -217,6 +218,9 @@ export function AgentTermPreview({
// A badge ahead of the connection badge, for the full-tab mount's
// link back to the agent's details.
leading?: ComponentChildren;
// Icon-only badges in the top right corner, for the full-tab mount's own
// controls (a group pane's maximize and close).
trailing?: ComponentChildren;
}) {
const paths = termPaths(agentName, subagent);
const { rows, connection, reconnect } = useSwarmTermStream(paths.stream);
@ -293,6 +297,9 @@ export function AgentTermPreview({
onClick={() => openTab({ href: paths.page, label: paths.label })}
/>
)}
{trailing !== undefined && (
<div class="ui-agent-term-preview-trailing">{trailing}</div>
)}
</div>
<div class="live terminal" ref={logRef} onScroll={handleScroll}>
{rows.length === 0 ? (

View file

@ -2,16 +2,17 @@
// `/agents/group/:id`: each pane is one agent's full terminal, split by
// dragging another agent's terminal tab (or a pane's header) onto the edge of
// a pane, or by the split button in a pane's header. A pane can also be one of
// an agent's subagents, put beside it by the subagents menu. Dividers resize, and the
// layout is saved in the browser (`useTermGroups`). A pane's × takes its agent
// out of the group; a group of one gives way to that agent's own terminal.
// an agent's subagents, put beside it by the subagents menu. Dividers resize,
// and the layout is saved in the browser (`useTermGroups`). A pane's close icon
// takes its agent out of the group, its maximize icon opens the agent on its
// own; a group of one gives way to that agent's own terminal.
//
// Registers its header tab like an agent terminal does, so a reload or a
// bookmark reopens it. The tab shows the group's name, or its agents.
import { useEffect } from "preact/hooks";
import { useLocation, type RouteComponentProps } from "wouter-preact";
import { Badge } from "@hive/shared/badge.js";
import { ExpandIcon } from "@hive/shared/icons.js";
import { CloseIcon, ExpandIcon } from "@hive/shared/icons.js";
import { AgentIcon } from "../../agent/AgentIcon.js";
import { SubagentsMenu } from "../../agent/terminal/SubagentsMenu.js";
import {
@ -185,10 +186,13 @@ export function AgentGroupPage({
)
}
/>
</>
}
trailing={
<>
<Badge
icon={<ExpandIcon />}
value="maximize"
title={`open ${key}'s terminal on its own`}
value={<ExpandIcon />}
title={`maximize: open ${key}'s terminal on its own`}
onClick={() =>
open({
href: termPaths(agent, subagent).page,
@ -197,9 +201,8 @@ export function AgentGroupPage({
}
/>
<Badge
variant="quiet"
value="×"
title={`take ${key} out of this group`}
value={<CloseIcon />}
title={`close: take ${key} out of this group`}
onClick={() =>
groups.update(id, (r) => removeAgent(r, key) ?? r)
}