From 727bfb775b4df3d9f8da5f69e42a00c6391d5ade Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 13 Sep 2026 18:38:04 +0200 Subject: [PATCH] swarm-ui: fold the agent terminal into AgentsPage's detail panel, not a route mara's steer on the issue, right as the previous commit's plan was posted: the swarm-level term isn't a separate page, it's part of the same pwa -- selecting an agent should show a small preview (no header, no input) below the existing detail fields. That's the whole MVP for this issue; the full terminal (+ sending input back to the agent) is explicit follow-up scope. AgentTermPage -> AgentTermPreview: drops the Panel wrapper, the "terminal" nav badge + wouter route, and the back-link -- just the inline preview, height-capped at 12em (@hive/shared/terminal.css's own default is a much taller 72vh/60em, sized for a dedicated page rather than a peek). --- frontend/packages/swarm-ui/src/App.tsx | 2 - .../src/pages/agents/AgentTermPage.css | 20 --------- .../src/pages/agents/AgentTermPreview.css | 21 +++++++++ ...AgentTermPage.tsx => AgentTermPreview.tsx} | 45 +++++++------------ .../swarm-ui/src/pages/agents/AgentsPage.tsx | 18 +++----- 5 files changed, 42 insertions(+), 64 deletions(-) delete mode 100644 frontend/packages/swarm-ui/src/pages/agents/AgentTermPage.css create mode 100644 frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.css rename frontend/packages/swarm-ui/src/pages/agents/{AgentTermPage.tsx => AgentTermPreview.tsx} (59%) diff --git a/frontend/packages/swarm-ui/src/App.tsx b/frontend/packages/swarm-ui/src/App.tsx index c8722811..99380be6 100644 --- a/frontend/packages/swarm-ui/src/App.tsx +++ b/frontend/packages/swarm-ui/src/App.tsx @@ -3,7 +3,6 @@ import { Route, Switch } from "wouter-preact"; import { Shell } from "./shell/Shell.js"; import { AgentsPage } from "./pages/agents/AgentsPage.js"; -import { AgentTermPage } from "./pages/agents/AgentTermPage.js"; import { ComponentsPage } from "./pages/ComponentsPage.js"; import { JobsPage } from "./pages/JobsPage.js"; import { HivesPage } from "./pages/HivesPage.js"; @@ -24,7 +23,6 @@ export function App() { - diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentTermPage.css b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPage.css deleted file mode 100644 index 83005f88..00000000 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentTermPage.css +++ /dev/null @@ -1,20 +0,0 @@ -/* — `.terminal-wrap`/`.live.terminal` come from - @hive/shared/terminal.css unstyled (its own default 32em max-height - + scroll is exactly right here, no page override needed). Just - spacing for the status badge + back link this page adds around it. */ -@import "@hive/shared/terminal.css"; - -.ui-agent-term-status { - display: inline-flex; - margin-block-end: 0.5rem; -} - -.ui-agent-term-back { - font-size: 0.85rem; - color: var(--fg); - text-decoration: none; -} - -.ui-agent-term-back:hover { - text-decoration: underline; -} diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.css b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.css new file mode 100644 index 00000000..069ff99c --- /dev/null +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.css @@ -0,0 +1,21 @@ +/* — a *preview*, not the full terminal: a much + shorter cap than @hive/shared/terminal.css's own 32em default so it + reads as "peek at what's happening" inside the detail panel rather + than competing with the panel's own fields for vertical space. */ +@import "@hive/shared/terminal.css"; + +.ui-agent-term-preview { + margin-block-start: 1rem; +} + +.ui-agent-term-preview-status { + margin-block-end: 0.5rem; +} + +.ui-agent-term-preview-wrap.terminal-wrap { + max-height: 12em; +} + +.ui-agent-term-preview-wrap .live.terminal { + max-height: 12em; +} diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentTermPage.tsx b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.tsx similarity index 59% rename from frontend/packages/swarm-ui/src/pages/agents/AgentTermPage.tsx rename to frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.tsx index 9d58c30b..1275a9fd 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentTermPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.tsx @@ -1,19 +1,16 @@ -// — read-only live terminal for one agent, consuming -// swarm-controller's `GET /api/agents/{name}/term/stream`. First -// per-agent detail route in swarm-ui (linked from `AgentsPage`'s detail -// panel). Live-only, no backfill/history — see `useSwarmTermStream`'s -// doc for why a fresh visit starts empty rather than replaying anything. -// -// Auto-scroll is the simple "stick to bottom unless the operator -// scrolled up" rule `@hive/agent`'s `LiveStream` uses, minus its -// load-more/history plumbing (nothing to load older here). +// — the MVP mara's own ruling asked for: not a separate +// page, a small read-only preview embedded below `AgentsPage`'s detail +// panel fields. No header, no input — those (plus sending input back +// to the agent) are explicitly follow-up scope, not this issue's. +// Consumes swarm-controller's `GET /api/agents/{name}/term/stream` via +// `useSwarmTermStream`, rendering through the same `@hive/shared` +// `Row` component the per-hive local terminal uses — "share components +// between hive and swarm level term as much as possible" (mara). import { useLayoutEffect, useRef, useState } from "preact/hooks"; -import { Link, useParams } from "wouter-preact"; import { Badge, type BadgeTone } from "@hive/shared/badge.js"; import { Row } from "@hive/shared/term-row.js"; -import { Panel } from "../../ui/panel/Panel.js"; import { useSwarmTermStream, type ConnectionState } from "./useSwarmTermStream.js"; -import "./AgentTermPage.css"; +import "./AgentTermPreview.css"; const NEAR_BOTTOM_PX = 48; @@ -31,10 +28,9 @@ const CONNECTION_TONE: Record = { closed: "negative", }; -export function AgentTermPage() { - const { name } = useParams<{ name: string }>(); +export function AgentTermPreview({ agentName }: { agentName: string }) { const { rows, connection } = useSwarmTermStream( - `/api/agents/${encodeURIComponent(name ?? "")}/term/stream`, + `/api/agents/${encodeURIComponent(agentName)}/term/stream`, ); const logRef = useRef(null); const [stickToBottom, setStickToBottom] = useState(true); @@ -48,29 +44,20 @@ export function AgentTermPage() { } // Runs after Preact has committed new rows, so `scrollHeight` is - // already current — same ordering `LiveStream`'s own layout effect - // relies on. + // already current — same ordering `@hive/agent`'s `LiveStream` uses. useLayoutEffect(() => { const el = logRef.current; if (el && stickToBottom) el.scrollTop = el.scrollHeight - el.clientHeight; }, [rows, stickToBottom]); return ( - - ← agents - - } - > +
-
+
{rows.length === 0 ? (
@@ -83,6 +70,6 @@ export function AgentTermPage() { )}
- +
); } diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx b/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx index 9017c417..d2641135 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx @@ -26,12 +26,12 @@ // see its own comment) — `viewMode` here just says which one to show. import { useState } from "preact/hooks"; import type { ComponentChildren } from "preact"; -import { useLocation } from "wouter-preact"; import { ApiErrorPanel } from "@hive/shared/api-error-panel.js"; import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js"; import { Badge } from "@hive/shared/badge.js"; import { LinkIcon } from "@hive/shared/icons.js"; import { AgentCard } from "./AgentCard.js"; +import { AgentTermPreview } from "./AgentTermPreview.js"; import { FRESHNESS, type AgentRow } from "./AgentTypes.js"; import { Button } from "../../ui/button/Button.js"; import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js"; @@ -96,7 +96,6 @@ type ViewMode = "cards" | "table"; const VIEW_MODE_KEY = "swarm-ui:agents:view-mode"; export function AgentsPage() { - const [, navigate] = useLocation(); const [rows, setRows] = useState(null); const [error, setError] = useState(null); const [intervalMs, setIntervalMs] = @@ -566,17 +565,6 @@ export function AgentsPage() {
- - navigate( - `/agents/${encodeURIComponent(detailTarget.name)}/term`, - ) - } - title={`view ${detailTarget.name}'s live terminal`} - /> } @@ -594,6 +582,10 @@ export function AgentsPage() { } />
+ {/* MVP scope per mara's own ruling: a small read-only + preview, no header/no input — the full terminal + (+ sending input back) is explicit follow-up scope. */} +
) : (

select an agent to see its details