Pure `nix fmt` output from the commit before this one — no hand edits. 203 files: 52 md, 42 tsx, 32 js, 32 css, 21 ts, 13 html, 8 json, 3 mjs. Reproduce with `nix develop -c nix fmt` on the parent commit; the result should be byte-identical to this tree. None of the 13 `.prettierignore` entries appears here — verified by intersecting the changed-file list against the ignore file, with a control proving the intersection finds a match when one exists.
99 lines
4.3 KiB
TypeScript
99 lines
4.3 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). Three columns: icon, main
|
|
// (title + hive-label), pills — see the `pills` prop doc below for why
|
|
// the third column is the one that grows. 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.
|
|
|
|
// Measures its own rendered height via ResizeObserver, writes it to
|
|
// `--agent-header-real-h` (agent.css's downstream consumers —
|
|
// `.agent-status-overlay`, `.agent-main`'s scroll padding — prefer
|
|
// this over the static `--agent-header-h` guess). A static guess was
|
|
// the root cause of two "login card behind the header" reports on
|
|
// this PR (mara, live): any header content that can wrap an extra
|
|
// line breaks the guess at whatever width triggers it, and no width
|
|
// can be promised safe. Deliberately a SEPARATE var, not an overwrite
|
|
// of `--agent-header-h` in place: `.agent-header`'s own `min-height`
|
|
// also reads that var — overwriting it in place would make the
|
|
// header's own size react to its own just-measured height every
|
|
// tick, a real ResizeObserver feedback loop (confirmed live on the
|
|
// first pass of this fix). `--agent-header-h` stays the static floor;
|
|
// `--agent-header-real-h` is output-only.
|
|
import { useEffect, useRef } from "preact/hooks";
|
|
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;
|
|
/** The status/badge row (`<StatusChips>`) — lives in the
|
|
* `.agent-header-pills` cluster alongside `pills` (see below), not
|
|
* stacked as its own row under the title. */
|
|
children?: ComponentChildren;
|
|
/** Flyout triggers (inbox/todos/links pills, the overflow menu
|
|
* button) plus `children` (the status badges), all in
|
|
* `.agent-header-pills`. `.agent-header-main` only ever holds the
|
|
* title + hive-label rows, which don't wrap in practice and so stay
|
|
* a stable height; only this pills-cluster column (already sized for
|
|
* a variable pill count) grows to fit badges+pills together, keeping
|
|
* the header a stable height even on widescreen. Meta-nav
|
|
* (`<MetaNav>`) lives here too, as a fixed-size trigger, not an
|
|
* inline link list in the title row (docs/web-ui/agent.md). */
|
|
pills?: ComponentChildren;
|
|
}
|
|
|
|
export function Header({ label, hiveLabel, children, pills }: HeaderProps) {
|
|
const ref = useRef<HTMLElement>(null);
|
|
|
|
useEffect(() => {
|
|
const el = ref.current;
|
|
if (!el || typeof ResizeObserver === "undefined") return;
|
|
// getBoundingClientRect() (not ResizeObserver's own contentRect) —
|
|
// we want the full visual box padding+border included, the thing
|
|
// content below the fixed header actually needs to clear, not the
|
|
// content-box-only measurement ResizeObserver's entry defaults to.
|
|
const ro = new ResizeObserver(() => {
|
|
document.documentElement.style.setProperty(
|
|
"--agent-header-real-h",
|
|
`${el.getBoundingClientRect().height}px`,
|
|
);
|
|
});
|
|
ro.observe(el);
|
|
return () => ro.disconnect();
|
|
}, []);
|
|
|
|
return (
|
|
<header class="agent-header" ref={ref}>
|
|
<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>
|
|
{children || pills ? (
|
|
<div class="agent-header-pills">
|
|
{children}
|
|
{pills}
|
|
</div>
|
|
) : null}
|
|
</header>
|
|
);
|
|
}
|