agent: remove rebuild button, move dashboard link into the links menu

mara (#3704): 'remove rebuild button, move link to dashboards into
links menu.'

The overflow (⋯) menu existed for exactly two items: the dashboard
back-link and a rebuild-container action. Rebuild is gone outright —
the dashboard's own R3BU1LD button already covers it, this was just a
rarely-used shortcut not worth its own menu. The dashboard link moves
into MetaNav's links popover (now the first item, above stats/forge/
config/extras) instead. With both gone, OverflowMenu had nothing left
to justify existing as a separate component — deleted along with its
CSS and the now-unused rebuildAction.ts (only consumer).

MetaNav gained a dashboardBase prop (Root.tsx already computes this
via resolveDashboardBase for InboxPanel/pause — reused, not
duplicated) and renders the dashboard link as a real <a>, same
treatment as every other item in that popover — no dangling
window.open()-only affordance.

Updated docs/web-ui/agent.md's Header section and the couple of
now-stale OverflowMenu references in index.html's/MetaNav.css's own
comments.

Verified: header now shows a single trailing icon-badge (was two),
popover opens with dashboard first then the agent_links() set.
tsc --noEmit clean, build clean, both pre-push lints clean.
This commit is contained in:
iris 2026-08-28 23:40:56 +02:00
commit 668ccc2278
8 changed files with 71 additions and 143 deletions

View file

@ -2,26 +2,27 @@
// ("🔗") in the pills cluster that opens a popover listing
// stats/screen/forge/config + any `hyperhive.dashboardLinks` extras,
// sourced from the backend's `agent_links()` (the single source of
// truth — same list also feeds the dashboard card's icon strip).
//
// truth — same list also feeds the dashboard card's icon strip) — plus
// a `↑ dashboard` back-link, formerly the overflow menu's job (mara:
// "remove rebuild button, move link to dashboards into links menu" —
// the overflow `⋯` trigger existed for exactly two items, dashboard-
// link and rebuild; rebuild's gone outright, the dashboard's own
// R3BU1LD button already covers it, dashboard-link moves here, so
// `OverflowMenu` had nothing left to justify existing and is deleted).
// v1 of this rendered every link inline in the header's title row —
// mara, live: "the login card looks like it is behind the header"
// (a variable-width link list can grow the title row past the fixed
// `--agent-header-h` the rest of the page is offset against — see
// Header.tsx's comment) and "the links should have the same popout as
// the links in the nav bar of swarm-ui." This is that: same shape as
// swarm-ui's own `LinksMenu` (Shell/LinksMenu.tsx) — one quiet icon
// trigger, a popover on click, real `<a>` items (not a command-dispatch
// button list) so ctrl/middle-click and "copy link address" keep
// working. Trigger itself reuses `@hive/shared`'s `Badge` (icon-only,
// same shape as the header's `⋯` overflow trigger — same touch target,
// same hover/expanded treatment, one visual language for every header
// trigger) rather than a bespoke button. The popover isn't built from
// `@hive/shared`'s `Dropdown` — its items are always `<button>`s
// (command dispatch), which would lose real link semantics — but
// matches its `.ui-dropdown` visual values exactly (see MetaNav.css),
// the same "reuse the values, not the component" call `LoginFlow`'s
// `.login-card` already made for the same reason.
// the links in the nav bar of swarm-ui." This is that: one quiet icon
// trigger (reuses `@hive/shared`'s `Badge`, icon-only), a popover on
// click, real `<a>` items — not `@hive/shared`'s `Dropdown` (its items
// are always `<button>`s for command dispatch, which would lose real
// link semantics like ctrl/middle-click and "copy link address") —
// but matching its `.ui-dropdown` visual values exactly (see
// MetaNav.css), the same "reuse the values, not the component" call
// `LoginFlow`'s `.login-card` already made for the same reason.
import { useEffect, useRef, useState } from 'preact/hooks';
import { Badge } from '@hive/shared/badge.js';
import type { AgentLink } from '../types.js';
@ -30,9 +31,12 @@ import './MetaNav.css';
export interface MetaNavProps {
links: AgentLink[];
forgePublicUrl: string | null;
/** Absolute base URL of the host dashboard (`resolveDashboardBase`)
* the `↑ dashboard` item links to `${dashboardBase}dashboard.html`. */
dashboardBase: string;
}
export function MetaNav({ links, forgePublicUrl }: MetaNavProps) {
export function MetaNav({ links, forgePublicUrl, dashboardBase }: MetaNavProps) {
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
@ -58,13 +62,24 @@ export function MetaNav({ links, forgePublicUrl }: MetaNavProps) {
// entirely (never guessed from `<host>:3000`); `external` is already
// absolute; `container` is a same-origin path.
const visible = links.filter((lnk) => lnk.kind !== 'forge' || forgePublicUrl);
if (visible.length === 0) return null;
// No early-return-on-empty any more — the dashboard link below is
// always present, so the trigger always has at least one item.
return (
<div class="meta-nav-anchor" ref={rootRef}>
<Badge value="🔗" title="agent links" onClick={() => setOpen((o) => !o)} expanded={open} />
{open ? (
<div class="meta-nav-popover" role="menu" aria-label="agent links">
<a
class="meta-nav-item"
href={`${dashboardBase}dashboard.html`}
target="_blank"
rel="noopener"
role="menuitem"
onClick={() => setOpen(false)}
>
dashboard
</a>
{visible.map((lnk) => {
const href = lnk.kind === 'forge' ? `${forgePublicUrl}${lnk.url}` : lnk.url;
return (