Watch
0
0
Fork
You've already forked hyperhive
0

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).
This commit is contained in:
iris 2026-09-13 18:38:04 +02:00 • committed by mara
commit 727bfb775b
5 changed files with 42 additions and 64 deletions

View file

@ -1,88 +0,0 @@
// <AgentTermPage> — 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).
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";
const NEAR_BOTTOM_PX = 48;
const CONNECTION_LABEL: Record<ConnectionState, string> = {
connecting: "connecting…",
open: "live",
reconnecting: "reconnecting…",
closed: "disconnected",
};
const CONNECTION_TONE: Record<ConnectionState, BadgeTone> = {
connecting: "neutral",
open: "positive",
reconnecting: "warning",
closed: "negative",
};
export function AgentTermPage() {
const { name } = useParams<{ name: string }>();
const { rows, connection } = useSwarmTermStream(
`/api/agents/${encodeURIComponent(name ?? "")}/term/stream`,
);
const logRef = useRef<HTMLDivElement>(null);
const [stickToBottom, setStickToBottom] = useState(true);
function handleScroll() {
const el = logRef.current;
if (!el) return;
setStickToBottom(
el.scrollHeight - el.scrollTop - el.clientHeight <= NEAR_BOTTOM_PX,
);
}
// Runs after Preact has committed new rows, so `scrollHeight` is
// already current — same ordering `LiveStream`'s own layout effect
// relies on.
useLayoutEffect(() => {
const el = logRef.current;
if (el && stickToBottom) el.scrollTop = el.scrollHeight - el.clientHeight;
}, [rows, stickToBottom]);
return (
<Panel
title={`terminal — ${name}`}
icon="🖥️"
actions={
<Link to="/agents" class="ui-agent-term-back">
← agents
</Link>
}
>
<Badge
class="ui-agent-term-status"
tone={CONNECTION_TONE[connection]}
value={CONNECTION_LABEL[connection]}
/>
<div class="terminal-wrap">
<div class="live terminal" ref={logRef} onScroll={handleScroll}>
{rows.length === 0 ? (
<div class="row note">
{connection === "open"
? "(connected — waiting for events)"
: CONNECTION_LABEL[connection]}
</div>
) : (
rows.map((row) => <Row key={row.key} row={row} />)
)}
</div>
</div>
</Panel>
);
}