From 4876acd02a43da9339d812d669961be3e3ca6b33 Mon Sep 17 00:00:00 2001 From: iris Date: Thu, 5 Nov 2026 13:33:12 +0100 Subject: [PATCH] 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. --- docs/swarm/ui.md | 11 +++++---- frontend/packages/shared/src/icons.tsx | 21 +++++++++++++++++ .../src/agent/terminal/AgentTermPreview.css | 6 +++++ .../src/agent/terminal/AgentTermPreview.tsx | 7 ++++++ .../src/pages/agents/AgentGroupPage.tsx | 23 +++++++++++-------- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md index 7caefe48..4603eec3 100644 --- a/docs/swarm/ui.md +++ b/docs/swarm/ui.md @@ -169,11 +169,12 @@ a group of the two, with its own header tab at `/agents/group/`. 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. diff --git a/frontend/packages/shared/src/icons.tsx b/frontend/packages/shared/src/icons.tsx index 74b63a36..cf0570a4 100644 --- a/frontend/packages/shared/src/icons.tsx +++ b/frontend/packages/shared/src/icons.tsx @@ -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 ( + + ); +} + // 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 diff --git a/frontend/packages/swarm-ui/src/agent/terminal/AgentTermPreview.css b/frontend/packages/swarm-ui/src/agent/terminal/AgentTermPreview.css index 1c4cc151..f86682de 100644 --- a/frontend/packages/swarm-ui/src/agent/terminal/AgentTermPreview.css +++ b/frontend/packages/swarm-ui/src/agent/terminal/AgentTermPreview.css @@ -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; diff --git a/frontend/packages/swarm-ui/src/agent/terminal/AgentTermPreview.tsx b/frontend/packages/swarm-ui/src/agent/terminal/AgentTermPreview.tsx index 905ec830..bd82feb8 100644 --- a/frontend/packages/swarm-ui/src/agent/terminal/AgentTermPreview.tsx +++ b/frontend/packages/swarm-ui/src/agent/terminal/AgentTermPreview.tsx @@ -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 && ( +
{trailing}
+ )}
{rows.length === 0 ? ( diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentGroupPage.tsx b/frontend/packages/swarm-ui/src/pages/agents/AgentGroupPage.tsx index 84a5580d..045b3af3 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentGroupPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentGroupPage.tsx @@ -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={ + <> } - value="maximize" - title={`open ${key}'s terminal on its own`} + value={} + title={`maximize: open ${key}'s terminal on its own`} onClick={() => open({ href: termPaths(agent, subagent).page, @@ -197,9 +201,8 @@ export function AgentGroupPage({ } /> } + title={`close: take ${key} out of this group`} onClick={() => groups.update(id, (r) => removeAgent(r, key) ?? r) }