swarm-ui: full-tab agent terminal (#4506)
Adds a full, non-capped agent terminal reachable from a new expand trigger on the embedded AgentTermPreview (the detail-panel preview on AgentsPage stays as-is, just gains the trigger). Opens /agents/:name/terminal in a new dynamic tab in Shell's header, next to the static nav row — tabs persist across a reload via useDynamicTabs, a small localStorage-backed hook built on @hive/shared's existing settings-storage primitive. AgentTermPreview gains two new props to support both mounts from one component: fullHeight (drops the 12em preview cap, fills its page) and showHeaderBadges (default true — lets a future caller that already shows turn_state/model/ctx/cost elsewhere suppress this cluster; AgentsPage doesn't use it, see below). Deviation from the originally posted plan (issue comment 80596): that plan proposed AgentsPage's embedded preview pass showHeaderBadges as false, reasoning the detail panel already duplicates that info. Checked the actual code before implementing — it doesn't; AgentRow/ AgentTypes.ts carry none of turn_state/model/ctx/cost, and AgentTermPreview's own floating badges are the only place swarm-ui shows them. Left the badges visible there instead of shipping a regression the plan's own stated justification didn't hold up to. Also fixed a same-tab pub/sub race found by actually rendering a cold load of /agents/:name/terminal (headless chromium, not just reasoning about the code): useLocalSetting subscribes inside a useEffect, and mount effects fire children-before-parents, so a descendant's mount-time write (AgentTerminalPage registering its own tab) can beat an ancestor's (Shell's) subscription into existence, leaving Shell's tab row silently empty on a direct/reload load. Fixed by having useDynamicTabs re-sync from storage on every location change, not just on notify() — the fix lives in the new hook itself, not in the shared settings-storage primitive theme/motion overrides also use.
This commit is contained in:
parent
433429ebfd
commit
c460e91bd1
1 changed files with 445 additions and 11 deletions
|
|
@ -165,6 +165,75 @@
|
|||
.shell-nav-indicator.shell-nav-indicator-instant {
|
||||
transition: none;
|
||||
}
|
||||
/* Dynamic tab row — sits below `.shell-header`, its own sticky
|
||||
strip rather than folded into the header itself: the header's own
|
||||
sticky offset (`top: 0`) is already claimed, and a header that grows
|
||||
taller every time a tab opens would shift `.shell-nav`'s measured
|
||||
positions (`measureIndex`, Shell.tsx) out from under the sliding
|
||||
indicator mid-animation. `top` below is `.shell-header`'s own
|
||||
footprint — `0.75em` padding top+bottom either side of the
|
||||
`2.75em`-min-height nav row (`.shell-nav-link`, this file), so ~4.25em
|
||||
rounded up to 4.5em for the 1px border + a little slack — not
|
||||
measured, same fixed-estimate tradeoff `AgentTerminalPage.css`'s own
|
||||
comment makes for the same reason (not worth a ResizeObserver for one
|
||||
sticky offset), and the same 4.5em value that file's own height calc
|
||||
already uses for this header, so the two stay consistent with each
|
||||
other rather than drifting to two different guesses at one thing.
|
||||
Undershooting this would mean the sticky header (z-index 10, above
|
||||
this row's 9) visibly covers the top of the tab row once scrolled —
|
||||
worth erring high, not low, if the real value ever drifts. */
|
||||
.shell-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
padding: 0.4em 1.25em;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-elev);
|
||||
position: sticky;
|
||||
top: 4.5em;
|
||||
z-index: 9; /* one below .shell-header's 10, so the header still wins if both ever overlap during a fast scroll */
|
||||
}
|
||||
.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 `<button>` case) — a bare `<button>` here isn't routed
|
||||
through `Badge` since it's not a status/value pill, just a close
|
||||
glyph, so the reset is inlined rather than reaching for a component
|
||||
that doesn't fit this shape. */
|
||||
.shell-tab-close {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 0.2em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.shell-tab-close:hover {
|
||||
color: var(--fg);
|
||||
background: color-mix(in srgb, var(--fg) 10%, transparent);
|
||||
}
|
||||
|
||||
.shell-body {
|
||||
max-width: 60em;
|
||||
margin: 0 auto;
|
||||
|
|
|
|||
Loading…
Reference in a new issue