swarm-ui: terminal tab fills the viewport below the header
The agent terminal tab now takes the whole window except the header bar, full width and height, instead of sitting in the padded content column. The connection badge floats inside the terminal's top-left corner and the toolbar row above it is gone on this route.
This commit is contained in:
parent
78e9f2ca67
commit
66df552f68
5 changed files with 87 additions and 51 deletions
|
|
@ -8,16 +8,11 @@
|
||||||
margin-block-start: 1rem;
|
margin-block-start: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Holds the connection-status badge + (unless `fullHeight`) the
|
/* Holds the connection-status badge and the expand-to-fullscreen
|
||||||
expand-to-fullscreen trigger — `margin-left: auto` targets the expand
|
trigger (absent from the `fullHeight` mount, which floats the status
|
||||||
trigger by its OWN class, not `:last-child` (tried that first; it
|
badge inside the terminal instead). `margin-left: auto` targets the
|
||||||
silently broke when `fullHeight` hides the expand trigger, since the
|
expand trigger by its own class rather than `:last-child`, same
|
||||||
status badge then becomes the `:last-child` too and gets pushed to
|
split-row intent `Shell.css`'s `.shell-header-actions` uses. */
|
||||||
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 {
|
.ui-agent-term-preview-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -51,6 +46,7 @@
|
||||||
.ui-agent-term-preview-full {
|
.ui-agent-term-preview-full {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
margin-block-start: 0;
|
||||||
}
|
}
|
||||||
.ui-agent-term-preview-full .ui-agent-term-preview-wrap.terminal-wrap {
|
.ui-agent-term-preview-full .ui-agent-term-preview-wrap.terminal-wrap {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
|
|
@ -61,6 +57,14 @@
|
||||||
max-height: none;
|
max-height: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
/* Flush to the header bar and the viewport sides; the input row below
|
||||||
|
gets its own padding so it doesn't touch the screen edge. */
|
||||||
|
.ui-agent-term-preview-full .ui-agent-term-preview-wrap.terminal-wrap {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.ui-agent-term-preview-full .ui-agent-input {
|
||||||
|
padding: 0.4em 0.75em 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
/* `box-sizing: border-box`, not the browser's default `content-box` —
|
/* `box-sizing: border-box`, not the browser's default `content-box` —
|
||||||
`.live.terminal`'s own `padding: 0.8em 1em 0.4em` (terminal.css) is
|
`.live.terminal`'s own `padding: 0.8em 1em 0.4em` (terminal.css) is
|
||||||
|
|
@ -105,3 +109,12 @@
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
max-width: calc(100% - 0.8em);
|
max-width: calc(100% - 0.8em);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Full-tab mount's connection badge — top-left floating twin of
|
||||||
|
`.ui-agent-term-preview-header-badges` (top-right), same anchor. */
|
||||||
|
.ui-agent-term-preview-live {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.4em;
|
||||||
|
left: 0.4em;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,13 +175,16 @@ export function AgentTermPreview({
|
||||||
(fullHeight ? " ui-agent-term-preview-full" : "")
|
(fullHeight ? " ui-agent-term-preview-full" : "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{/* The full-tab mount has no toolbar row: the terminal takes the
|
||||||
|
whole page below the shell header, and the connection badge
|
||||||
|
floats inside it with the other badges (below). */}
|
||||||
|
{!fullHeight && (
|
||||||
<div class="ui-agent-term-preview-toolbar">
|
<div class="ui-agent-term-preview-toolbar">
|
||||||
<Badge
|
<Badge
|
||||||
class="ui-agent-term-preview-status"
|
class="ui-agent-term-preview-status"
|
||||||
tone={CONNECTION_TONE[connection]}
|
tone={CONNECTION_TONE[connection]}
|
||||||
value={CONNECTION_LABEL[connection]}
|
value={CONNECTION_LABEL[connection]}
|
||||||
/>
|
/>
|
||||||
{!fullHeight && (
|
|
||||||
<Badge
|
<Badge
|
||||||
class="ui-agent-term-preview-expand"
|
class="ui-agent-term-preview-expand"
|
||||||
variant="quiet"
|
variant="quiet"
|
||||||
|
|
@ -190,9 +193,19 @@ export function AgentTermPreview({
|
||||||
title={`open ${paths.label}'s terminal in its own tab`}
|
title={`open ${paths.label}'s terminal in its own tab`}
|
||||||
onClick={() => openTab({ href: paths.page, label: paths.label })}
|
onClick={() => openTab({ href: paths.page, label: paths.label })}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div class="terminal-wrap ui-agent-term-preview-wrap">
|
<div class="terminal-wrap ui-agent-term-preview-wrap">
|
||||||
|
{/* Full-tab mount only: the connection badge floats in the
|
||||||
|
terminal's top-left corner (the other badges own the
|
||||||
|
top-right), instead of sitting in a toolbar row above it. */}
|
||||||
|
{fullHeight && (
|
||||||
|
<Badge
|
||||||
|
class="ui-agent-term-preview-status ui-agent-term-preview-live"
|
||||||
|
tone={CONNECTION_TONE[connection]}
|
||||||
|
value={CONNECTION_LABEL[connection]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{header && showHeaderBadges && (
|
{header && showHeaderBadges && (
|
||||||
<div class="ui-agent-term-preview-header-badges">
|
<div class="ui-agent-term-preview-header-badges">
|
||||||
<Badge
|
<Badge
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,13 @@
|
||||||
/* <AgentTerminalPage> — gives `AgentTermPreview`'s `fullHeight` flex
|
/* <AgentTerminalPage> — fills `.shell-body-full` (Shell.css), which is
|
||||||
chain (`.ui-agent-term-preview-full`, AgentTermPreview.css) something
|
already everything below the header bar, full width and height. This
|
||||||
real to fill. `Shell.css`'s `.shell-body` has no fixed height of its
|
wrapper only has to pass that height down so `AgentTermPreview`'s
|
||||||
own (it's a plain flow column sized by content), so without an
|
`fullHeight` flex chain (`.ui-agent-term-preview-full`) has a real box
|
||||||
explicit height here the chain's `flex: 1` would resolve against
|
to fill. */
|
||||||
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 {
|
.ui-agent-terminal-page {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: calc(100dvh - 4.5em - 3em);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-agent-terminal-page .ui-agent-term-preview-full {
|
.ui-agent-terminal-page .ui-agent-term-preview-full {
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,27 @@
|
||||||
.shell-body-wide {
|
.shell-body-wide {
|
||||||
max-width: 90em;
|
max-width: 90em;
|
||||||
}
|
}
|
||||||
|
/* Full-bleed route (the agent terminal): the shell becomes a viewport-high
|
||||||
|
column, the header keeps its natural height, and the body takes every
|
||||||
|
remaining pixel with no width cap or padding. `100dvh`, not `100vh` —
|
||||||
|
`vh` includes mobile browser chrome that is on screen. */
|
||||||
|
.shell-full {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
.shell-full .shell-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.shell-body-full {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
@keyframes shell-page-enter {
|
@keyframes shell-page-enter {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
|
||||||
|
|
@ -63,19 +63,19 @@ const NAV_ITEMS: { href: string; label: string; accent: string }[] = [
|
||||||
const WIDE_BODY_ROUTES = new Set(["/issues", "/agents"]);
|
const WIDE_BODY_ROUTES = new Set(["/issues", "/agents"]);
|
||||||
|
|
||||||
// `/agents/:name/terminal` (and a subagent's, under
|
// `/agents/:name/terminal` (and a subagent's, under
|
||||||
// `/agents/:name/subagents/:subagent/`) needs the same widening but can't join
|
// `/agents/:name/subagents/:subagent/`) is full-bleed: the terminal fills
|
||||||
// the `Set` above — it's a parameterized path, one literal string can't
|
// the whole viewport below the header bar, edge to edge (`shell-full` /
|
||||||
// match every agent name. A 60em-capped terminal pane reads as cramped
|
// `shell-body-full` in Shell.css). It can't join the `Set` above — it's a
|
||||||
// (this is a full tab, not a sidebar preview), same reasoning as the
|
// parameterized path, one literal string can't match every agent name.
|
||||||
// table routes above just for a different reason (a wide pane, not a
|
const FULL_BLEED_ROUTE_PATTERN =
|
||||||
// wide table).
|
|
||||||
const WIDE_BODY_ROUTE_PATTERN =
|
|
||||||
/^\/agents\/[^/]+(\/subagents\/[^/]+)?\/terminal$/;
|
/^\/agents\/[^/]+(\/subagents\/[^/]+)?\/terminal$/;
|
||||||
|
|
||||||
|
function isFullBleedRoute(location: string): boolean {
|
||||||
|
return FULL_BLEED_ROUTE_PATTERN.test(location);
|
||||||
|
}
|
||||||
|
|
||||||
function isWideBodyRoute(location: string): boolean {
|
function isWideBodyRoute(location: string): boolean {
|
||||||
return (
|
return WIDE_BODY_ROUTES.has(location);
|
||||||
WIDE_BODY_ROUTES.has(location) || WIDE_BODY_ROUTE_PATTERN.test(location)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static fallback — matches `index.html`'s `<title>` default, so a page
|
// Static fallback — matches `index.html`'s `<title>` default, so a page
|
||||||
|
|
@ -360,7 +360,7 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
||||||
const navAccent = indicator?.accent ?? "var(--purple)";
|
const navAccent = indicator?.accent ?? "var(--purple)";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="shell">
|
<div class={"shell" + (isFullBleedRoute(location) ? " shell-full" : "")}>
|
||||||
<header class="shell-header">
|
<header class="shell-header">
|
||||||
<span
|
<span
|
||||||
class="shell-brand"
|
class="shell-brand"
|
||||||
|
|
@ -447,7 +447,9 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
||||||
apply, only the max-width cap changes. */}
|
apply, only the max-width cap changes. */}
|
||||||
<div
|
<div
|
||||||
class={
|
class={
|
||||||
"shell-body" + (isWideBodyRoute(location) ? " shell-body-wide" : "")
|
"shell-body" +
|
||||||
|
(isWideBodyRoute(location) ? " shell-body-wide" : "") +
|
||||||
|
(isFullBleedRoute(location) ? " shell-body-full" : "")
|
||||||
}
|
}
|
||||||
key={location}
|
key={location}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue