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 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** 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: 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 **right** or **below** puts it beside the pane. A pane can hold a subagent's
agent's own terminal in its tab, or goes to the tab when one is open already. A pane terminal (its header reads `agent/subagent`), put there by the **subagents**
can hold a subagent's terminal (its header reads `agent/subagent`), put there by badge in the pane's header. Two icon buttons sit in a pane's top right corner.
the **subagents** badge in the pane's header. The **×** takes the pane's **Maximize** (the arrows pointing to the corners) opens that pane's own terminal
agent out of the group, and a group left with one agent goes, landing on that 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 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 keys. Panes have no minimum size; a divider stops at 2% so a pane can't
vanish. 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 // A funnel, the standard "filter" glyph — smaller than the other two
// (1em not 1.3em) since its first caller sits inside a table header // (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 // cell, not a nav trigger button; scale via the caller's own

View file

@ -102,6 +102,12 @@
.ui-agent-term-preview-controls > * { .ui-agent-term-preview-controls > * {
pointer-events: auto; 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 { .ui-agent-term-preview-header-badges {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View file

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

View file

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