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

@ -0,0 +1,75 @@
// <AgentTermPreview> — 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 { Badge, type BadgeTone } from "@hive/shared/badge.js";
import { Row } from "@hive/shared/term-row.js";
import { useSwarmTermStream, type ConnectionState } from "./useSwarmTermStream.js";
import "./AgentTermPreview.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 AgentTermPreview({ agentName }: { agentName: string }) {
const { rows, connection } = useSwarmTermStream(
`/api/agents/${encodeURIComponent(agentName)}/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 `@hive/agent`'s `LiveStream` uses.
useLayoutEffect(() => {
const el = logRef.current;
if (el && stickToBottom) el.scrollTop = el.scrollHeight - el.clientHeight;
}, [rows, stickToBottom]);
return (
<div class="ui-agent-term-preview">
<Badge
class="ui-agent-term-preview-status"
tone={CONNECTION_TONE[connection]}
value={CONNECTION_LABEL[connection]}
/>
<div class="terminal-wrap ui-agent-term-preview-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>
</div>
);
}