Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/frontend/packages/agent/src/components/Header.tsx
iris 3ce961e5bd agent: meta-nav popout + header height fix + badge row consistency
mara, live review: 'the login card looks like it is behind the header',
'badges and interactive elements on same row have inconsistent size',
'the links should have the same popout as the links in the nav bar of
swarm-ui.' All three, addressed:

- Root cause of the overlap: MetaNav rendered every link inline in the
  header's title row, a variable-width list that can grow the row past
  the fixed --agent-header-h the rest of the page (position: fixed
  header, position: absolute content below it) is offset against, so
  the header visually covers whatever's underneath it once it's
  actually taller than assumed. Fix: MetaNav is now a single
  fixed-size trigger (reuses @hive/shared's Badge, icon-only, same
  shape as the header's existing overflow trigger) in the pills
  cluster, not a variable-width list in the title row. A single badge
  can't grow the row regardless of how many links the backend sends.
  Verified at 1024px/1400px (no overlap, was previously untested below
  1400px) — the deeper fragility (StatusChips' badge row itself can
  still wrap on a genuinely narrow/mobile viewport and hit the same
  class of bug) is real but predates this PR and is out of scope here.
- The popover mara asked for: same visual language as swarm-ui's own
  LinksMenu (Shell/LinksMenu.tsx) and this page's Dropdown/OverflowMenu
  — quiet icon trigger, popover on click. Not built from @hive/shared's
  Dropdown itself (its items are always <button>s for command dispatch,
  which would lose real <a> link semantics — ctrl/middle-click, copy
2026-08-28 22:05:13 +02:00

60 lines
2.8 KiB
TypeScript

// <Header> — the agent page's identity strip: icon, glyphic title, the
// "swarm / hive" label row, and a slot for the status row
// (`<StatusChips>` — ../status-chips/, kept separate so this component
// has no opinion on what badges exist). Structurally the same
// three-row shape as the old `#agent-header` markup in index.html
// (icon · main · pills) — see `docs/web-ui.md::Per-agent page` — this
// is a like-for-like layout port, the redesign work is in the children.
// Reuses `agent.css`'s existing `.agent-header*`/`.agent-icon` rules
// (loaded globally by index.html) rather than a component-scoped
// stylesheet — no new visual language needed for the chrome itself.
import type { ComponentChildren } from 'preact';
// Icon 404s when the agent has no `hyperhive.icon` override (no
// bundled server-side default any more — see
// `hive_agent::web_ui::screen::serve_icon`). Ports app.js's
// `bindHeaderIconFallback` exactly: fire-and-forget load, swap to
// `/favicon.svg` on failure, `dataset.fallback` guards against a
// 404-on-the-fallback-itself loop.
function handleIconError(e: Event) {
const img = e.currentTarget as HTMLImageElement;
if (img.dataset.fallback) return;
img.dataset.fallback = '1';
img.src = '/favicon.svg';
}
export interface HeaderProps {
label: string;
hiveLabel?: string | null;
children?: ComponentChildren;
/** Right-cluster flyout triggers (inbox/todos/links pills, the
* overflow menu button) — mirrors the old markup's
* `.agent-header-pills` third column. Meta-nav (`<MetaNav>`) lives
* here too, as a fixed-size trigger — NOT in the title row the old
* markup put it in (`<nav id="meta-links">`, docs/web-ui/agent.md).
* A variable-width inline link list there grows the title row's
* actual height past the fixed `--agent-header-h` the rest of the
* page's `position: fixed` header + `position: absolute` content
* below it are offset against, so on a narrow viewport the header
* silently overlaps the login-recovery card underneath it (mara,
* live: "the login card looks like it is behind the header"). A
* fixed-size trigger button can't do that regardless of how many
* links the backend sends. */
pills?: ComponentChildren;
}
export function Header({ label, hiveLabel, children, pills }: HeaderProps) {
return (
<header class="agent-header">
<img class="agent-icon" src="icon" alt="" onError={handleIconError} />
<div class="agent-header-main">
<div class="agent-header-row agent-header-title-row">
<h2 class="agent-header-title">◆ {label} ◆</h2>
</div>
{hiveLabel ? <div class="agent-header-row agent-hive-row">{hiveLabel}</div> : null}
<div class="agent-header-row">{children}</div>
</div>
{pills ? <div class="agent-header-pills">{pills}</div> : null}
</header>
);
}