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.
107 lines
4.3 KiB
TypeScript
107 lines
4.3 KiB
TypeScript
// Small inline-SVG icons shared across pages — deliberately NOT emoji
|
|
// glyphs for a trigger button's own icon. mara, live, on swarm-ui's
|
|
// original links/settings buttons: "links and settings icon styles
|
|
// different / all different sizes" — an emoji glyph renders via the
|
|
// OS/browser's colour-emoji font at that font's own fixed metrics,
|
|
// ignoring `font-size`/`color`, so it can never match a neighbouring
|
|
// icon in size or weight on any platform (and some codepoints, like the
|
|
// gear `⚙`, aren't even reliably covered by every font — showing as a
|
|
// missing-glyph box or an oddly-weighted fallback depending on what's
|
|
// installed). Same `stroke`/`viewBox`/size shape for every icon here so
|
|
// they read as one rendering path wherever they're mounted — Feather/
|
|
// lucide-style line icons, `currentColor` stroke so they pick up
|
|
// whatever text colour the surrounding `Badge`/button already has.
|
|
//
|
|
// Originated in swarm-ui's `shell/SettingsMenu.tsx` (gear) and
|
|
// `shell/LinksMenu.tsx` (link chain) as separate inline copies; moved
|
|
// here once the agent page's `MetaNav`/`SettingsMenu` needed the exact
|
|
// same icons too (mara: the settings trigger went back to a text glyph
|
|
// when `SettingsMenu` was extracted into `@hive/shared`, undoing this
|
|
// fix — restoring the SVG here, in the shared component this time,
|
|
// fixes it for both pages at once instead of just one).
|
|
export function GearIcon() {
|
|
return (
|
|
<svg
|
|
width="1.3em"
|
|
height="1.3em"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
aria-hidden="true"
|
|
>
|
|
<circle cx="12" cy="12" r="3" />
|
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export function LinkIcon() {
|
|
return (
|
|
<svg
|
|
width="1.3em"
|
|
height="1.3em"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
aria-hidden="true"
|
|
>
|
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
// 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 (
|
|
<svg
|
|
width="1.3em"
|
|
height="1.3em"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
aria-hidden="true"
|
|
>
|
|
<polyline points="15 3 21 3 21 9" />
|
|
<polyline points="9 21 3 21 3 15" />
|
|
<line x1="21" y1="3" x2="14" y2="10" />
|
|
<line x1="3" y1="21" x2="10" y2="14" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
// 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
|
|
// `font-size` like the others rather than a hardcoded size prop, same
|
|
// reasoning as `GearIcon`/`LinkIcon` above (`currentColor` stroke).
|
|
export function FilterIcon() {
|
|
return (
|
|
<svg
|
|
width="1em"
|
|
height="1em"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
aria-hidden="true"
|
|
>
|
|
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
|
|
</svg>
|
|
);
|
|
}
|