agent: meta-nav, favicon fallback, document.title

Ports the 3 remaining gaps argus's lost-functionality audit found beyond
login: the header meta-nav strip (stats/screen/forge/config +
hyperhive.dashboardLinks extras — the only on-page path to those, not
just polish), the header icon's /favicon.svg fallback on a broken
image, and the browser tab title update. All three are straight ports
of app.js's existing logic (refreshState's meta-links loop,
bindHeaderIconFallback, setHeader's document.title block), same
kind -> URL resolution rules, same dataset.fallback loop guard, same
qualified_label/hive_name fallback chain — no new backend fields
needed, hive_agent::web_ui::state::StateSnapshot already serves
links/forge_public_url and useAgentState's whole-payload cast already
threads them to Root.

New <MetaNav> component (reuses agent.css's existing .agent-nav/
.agent-nav-link rules verbatim, no new CSS) renders in Header's title
row via a new nav prop. Favicon fallback lives in Header itself
(onError handler on the .agent-icon <img>). document.title is a
useEffect in Root keyed on label/qualified_label/hive_name.

tsc --noEmit clean, build clean, both pre-push lints clean. Screenshot
verifies meta-nav renders 4 links (stats/forge/config/extra, forge
kind resolving against a mocked forge_public_url) and the favicon
fallback firing against a deliberately-404'd /icon.
This commit is contained in:
iris 2026-08-28 20:54:11 +02:00
commit df78236ce4
4 changed files with 102 additions and 4 deletions

View file

@ -10,23 +10,41 @@
// 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;
/** Meta-nav links (stats/screen/forge/config/extras) `<MetaNav>`
* lands here, in the title row alongside the `<h2>`, same as the old
* markup's `<nav id="meta-links">` (docs/web-ui/agent.md::Header). */
nav?: ComponentChildren;
/** Right-cluster flyout triggers (inbox/todos pills today, the
* overflow menu button lands here in a later commit) mirrors the
* old markup's `.agent-header-pills` third column. */
pills?: ComponentChildren;
}
export function Header({ label, hiveLabel, children, pills }: HeaderProps) {
export function Header({ label, hiveLabel, children, nav, pills }: HeaderProps) {
return (
<header class="agent-header">
<img class="agent-icon" src="icon" alt="" />
<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>
{nav}
</div>
{hiveLabel ? <div class="agent-header-row agent-hive-row">{hiveLabel}</div> : null}
<div class="agent-header-row">{children}</div>