diff --git a/frontend/packages/shared/src/icons.tsx b/frontend/packages/shared/src/icons.tsx index a53b68ca..01b2b3eb 100644 --- a/frontend/packages/shared/src/icons.tsx +++ b/frontend/packages/shared/src/icons.tsx @@ -57,32 +57,6 @@ export function LinkIcon() { ); } -// Four corner-arrows, the standard "expand to fullscreen" glyph — added -// for the terminal-preview-to-full-tab trigger (`AgentTermPreview`'s own -// expand affordance). Same size/stroke recipe as `LinkIcon`/`GearIcon` -// above, not a smaller variant like `FilterIcon` — this one sits as a -// standalone `Badge` trigger, not inside a table header cell. -export function ExpandIcon() { - 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/App.tsx b/frontend/packages/swarm-ui/src/App.tsx index 8c1a482f..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 { AgentTerminalPage } from "./pages/agents/AgentTerminalPage.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/AgentTermPreview.css b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.css index c6ea8838..666f90fc 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.css +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.css @@ -8,60 +8,14 @@ margin-block-start: 1rem; } -/* Holds the connection-status badge + (unless `fullHeight`) the - expand-to-fullscreen trigger — `margin-left: auto` targets the expand - trigger by its OWN class, not `:last-child` (tried that first; it - silently broke when `fullHeight` hides the expand trigger, since the - status badge then becomes the `:last-child` too and gets pushed to - the right edge instead of staying flush left — caught by actually - rendering the `fullHeight` case, not just reasoning about the CSS). - Same split-row intent `Shell.css`'s `.shell-header-actions` already - uses for its own pair of triggers, just addressed directly instead of - positionally. */ -.ui-agent-term-preview-toolbar { - display: flex; - align-items: center; +.ui-agent-term-preview-status { margin-block-end: 0.5rem; } -.ui-agent-term-preview-expand { - margin-left: auto; -} .ui-agent-term-preview-wrap.terminal-wrap { max-height: 12em; } -/* `AgentTerminalPage`'s full-tab mount (`fullHeight` prop → the - `-full` modifier on the OUTER `.ui-agent-term-preview` root, not the - wrap) — self-contained flex chain, not dependent on the page around - it doing anything beyond giving this root a real height to fill (see - AgentTerminalPage.css). Column flex so the toolbar keeps its natural - height and the terminal-wrap below it takes the rest; `flex: 1` + - `min-height: 0` on the wrap (not `height: 100%`) is deliberate — a - flex item's `height` percentage doesn't account for a sibling - (the toolbar) already consuming space the way `flex: 1` does, and - `min-height: 0` is what actually lets the item shrink below its - content's natural height so `.live.terminal`'s own `overflow-y: auto` - (terminal.css) scrolls instead of the whole column growing past its - container. `!important`-free — the `max-height: none` overrides - below already carry one more class in the selector than the - un-modified rules above (`.ui-agent-term-preview-full` prefixing - each), so they're strictly more specific and win regardless of - source order. */ -.ui-agent-term-preview-full { - display: flex; - flex-direction: column; -} -.ui-agent-term-preview-full .ui-agent-term-preview-wrap.terminal-wrap { - max-height: none; - flex: 1; - min-height: 0; -} -.ui-agent-term-preview-full .ui-agent-term-preview-wrap .live.terminal { - max-height: none; - height: 100%; -} - /* `box-sizing: border-box`, not the browser's default `content-box` — `.live.terminal`'s own `padding: 0.8em 1em 0.4em` (terminal.css) is otherwise added ON TOP of this `max-height`, so the box renders diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.tsx b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.tsx index fdd0ff48..b6e113a1 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.tsx +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentTermPreview.tsx @@ -18,28 +18,9 @@ // here needs to be interactive. Sourced from `useSwarmAgentStateStream`, // not from `AgentRow` — the roster fetch `AgentsPage` already did has no // notion of turn_state/model/ctx/cost, only `wanted`/`snapshot.running`. -// `showHeaderBadges` (default true) exists for a caller that already -// shows this info elsewhere and wants this cluster suppressed — checked -// against the original plan for this page, which proposed exactly that -// for `AgentsPage`'s embedded usage; that page's detail panel turned out -// NOT to duplicate any of this (`AgentRow`/`AgentTypes.ts` carry none of -// turn_state/model/ctx/cost), so `AgentsPage` leaves this at its default -// rather than hiding the only place swarm-ui shows it — see the commit -// message for the full reasoning. The prop still exists because -// suppressing it is a real, reusable need in principle, just not here. - -// **Expand-to-fullscreen trigger** — opens/navigates to -// `/agents/:name/terminal` (`AgentTerminalPage`) via `useDynamicTabs`, -// same header-tab mechanism `Shell`'s own nav row now shares. Lives here -// (not on `AgentsPage`) so both the embedded preview and the future full -// page mount the identical trigger without either needing to know where -// the other one is. Suppressed via `fullHeight` — the full-page mount -// already IS the destination, so it has nothing further to expand to. import { useLayoutEffect, useRef, useState } from "preact/hooks"; import { Badge, type BadgeTone } from "@hive/shared/badge.js"; -import { ExpandIcon } from "@hive/shared/icons.js"; import { Row } from "@hive/shared/term-row.js"; -import { useDynamicTabs } from "../../shell/useDynamicTabs.js"; import { useSwarmTermStream, type ConnectionState, @@ -100,22 +81,13 @@ function fmtTokens(n: number): string { return String(n); } -export function AgentTermPreview({ - agentName, - showHeaderBadges = true, - fullHeight = false, -}: { - agentName: string; - showHeaderBadges?: boolean; - fullHeight?: boolean; -}) { +export function AgentTermPreview({ agentName }: { agentName: string }) { const { rows, connection } = useSwarmTermStream( `/api/agents/${encodeURIComponent(agentName)}/term/stream`, ); const { header } = useSwarmAgentStateStream( `/api/agents/${encodeURIComponent(agentName)}/state/stream`, ); - const { open: openTab } = useDynamicTabs(); const logRef = useRef(null); const [stickToBottom, setStickToBottom] = useState(true); @@ -138,36 +110,14 @@ export function AgentTermPreview({ }, [rows, stickToBottom]); return ( -
-
- - {!fullHeight && ( - } - value="expand" - title={`open ${agentName}'s terminal in its own tab`} - onClick={() => - openTab({ - href: `/agents/${encodeURIComponent(agentName)}/terminal`, - label: agentName, - }) - } - /> - )} -
+
+
- {header && showHeaderBadges && ( + {header && (
— gives `AgentTermPreview`'s `fullHeight` flex - chain (`.ui-agent-term-preview-full`, AgentTermPreview.css) something - real to fill. `Shell.css`'s `.shell-body` has no fixed height of its - own (it's a plain flow column sized by content), so without an - explicit height here the chain's `flex: 1` would resolve against - nothing and the terminal would collapse to its content's own height - instead of filling the viewport — the opposite of what "full-height" - mount means for this page specifically (the 12em-capped preview - inside `AgentsPage` deliberately has no such wrapper; this is the one - place that needs it). - - `100dvh`, not `100vh` — `100vh` on a mobile browser includes the - address-bar chrome even while it's visibly on-screen, so the terminal - would render taller than the actually-visible viewport there; - `100dvh` tracks the real visible area instead. The subtraction below - is a fixed estimate (Shell.css's own `.shell-header` padding + - `.shell-body`'s), not a measured value — an exact fit isn't worth a - ResizeObserver for a single full-bleed page; a few pixels short of - the viewport edge reads as intentional breathing room, not a bug. */ -.ui-agent-terminal-page { - display: flex; - flex-direction: column; - height: calc(100dvh - 4.5em - 3em); -} - -.ui-agent-terminal-page .ui-agent-term-preview-full { - flex: 1; - min-height: 0; -} diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentTerminalPage.tsx b/frontend/packages/swarm-ui/src/pages/agents/AgentTerminalPage.tsx deleted file mode 100644 index 4b1f8b9b..00000000 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentTerminalPage.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// — the swarm terminal tab's full page, mounted at -// `/agents/:name/terminal`. The MVP `AgentTermPreview` (below `AgentsPage`'s -// detail panel) is explicitly capped and read-only; this is that same -// component's `fullHeight` mount — no 12em cap, floating header badges -// shown (there's no detail-panel `
` here to duplicate them). Sending -// input back to the agent is still explicit follow-up scope, same as the -// preview's own file comment says — this page doesn't change that. -// -// Registers itself in the dynamic-tabs row on mount (not just when opened -// via the preview's expand trigger) — a bookmark, a shared link, or a -// plain page reload should all re-open the tab instead of landing on a -// page with no way back into the tab row. `useDynamicTabs().open()` is -// idempotent (dedupes by href), so this is safe to call every mount. -import { useEffect } from "preact/hooks"; -import type { RouteComponentProps } from "wouter-preact"; -import { AgentTermPreview } from "./AgentTermPreview.js"; -import { useDynamicTabs } from "../../shell/useDynamicTabs.js"; -import "./AgentTerminalPage.css"; - -export function AgentTerminalPage({ - params, -}: RouteComponentProps<{ name: string }>) { - const { open } = useDynamicTabs(); - const name = decodeURIComponent(params.name); - - // Runs once per mounted agent name — re-registering on every render - // would just be redundant work against an already-deduped list, but - // there's no reason to pay it more than once per identity. - useEffect(() => { - open({ href: `/agents/${encodeURIComponent(name)}/terminal`, label: name }); - // eslint-disable-next-line react-hooks/exhaustive-deps -- `open` is a - // fresh function identity every render (`useDynamicTabs` doesn't - // memoize it); including it would re-fire this effect every render - // instead of once per agent `name`. - }, [name]); - - return ( -
- -
- ); -} diff --git a/frontend/packages/swarm-ui/src/shell/Shell.css b/frontend/packages/swarm-ui/src/shell/Shell.css index 0393f5bc..fe1fd3e6 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.css +++ b/frontend/packages/swarm-ui/src/shell/Shell.css @@ -165,64 +165,6 @@ .shell-nav-indicator.shell-nav-indicator-instant { transition: none; } -/* Dynamic tabs group — inline in `.shell-nav` itself, next to the - static route links (mara, PR review: "the dynamic tab should be in - the top bar, not a new one below" — an earlier cut had this as its - own sticky row under the header; that version and the reasoning for - it are still in this file's git history if the split ever needs to - come back). `margin-left: 0.5em` + `border-left` reads as "a group - attached to the nav, not another nav item" without a second landmark - or a fixed pixel gap that'd fight `.shell-nav`'s own `gap`. */ -.shell-tabs { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 0.5em; - margin-left: 0.5em; - padding-left: 0.75em; - border-left: 1px solid var(--border); -} -.shell-tab { - display: flex; - align-items: center; - gap: 0.25em; - padding: 0.2em 0.4em 0.2em 0.7em; - border-radius: 0.3em; - background: var(--bg); - border: 1px solid var(--border); -} -.shell-tab-active { - border-color: var(--pink); -} -.shell-tab-link { - color: var(--muted); - text-decoration: none; - font-size: 0.9em; -} -.shell-tab-active .shell-tab-link { - color: var(--fg); -} -.shell-tab-link:hover { - color: var(--fg); -} -/* Same reset every icon-only chrome button in this codebase uses - (`Badge`'s own `