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:
parent
b56696d898
commit
df78236ce4
4 changed files with 102 additions and 4 deletions
|
|
@ -4,9 +4,10 @@
|
|||
// `needs_login_in_progress` recovery flow (`LoginFlow`) — an agent with
|
||||
// no credentials has no other web-UI path back online, so this isn't
|
||||
// optional polish.
|
||||
import { useRef, useState } from 'preact/hooks';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import type { BadgeTone } from '@hive/shared/badge.js';
|
||||
import { Header } from './components/Header.js';
|
||||
import { MetaNav } from './components/MetaNav.js';
|
||||
import { StatusChips } from './components/StatusChips.js';
|
||||
import { LiveStream, type LiveStreamHandle } from './components/LiveStream.js';
|
||||
import { HeaderPill } from './components/HeaderPill.js';
|
||||
|
|
@ -57,6 +58,22 @@ export function Root() {
|
|||
const { todos, refresh: refreshTodos } = useTodos();
|
||||
const [openPanel, setOpenPanel] = useState<OpenPanel>(null);
|
||||
const liveStreamRef = useRef<LiveStreamHandle>(null);
|
||||
|
||||
// Browser tab title — ports app.js's setHeader logic verbatim: prefer
|
||||
// the human display name + hive_name ("iris // pr1ma") so tabs read
|
||||
// naturally; fall back to the qualified domain label for multi-hive
|
||||
// disambiguation when hive_name is unset, then to plain label for
|
||||
// single-hive deploys. Without this every agent's tab reads the
|
||||
// static index.html <title> forever — can't tell tabs apart with
|
||||
// several open.
|
||||
useEffect(() => {
|
||||
if (!state) return;
|
||||
const tab =
|
||||
state.qualified_label && state.qualified_label !== state.label
|
||||
? state.qualified_label
|
||||
: state.label;
|
||||
document.title = state.hive_name ? `${state.label} // ${state.hive_name}` : `${tab} // hyperhive`;
|
||||
}, [state?.label, state?.qualified_label, state?.hive_name]);
|
||||
const termInput = (
|
||||
<footer class="agent-composer">
|
||||
<TermInput
|
||||
|
|
@ -140,7 +157,12 @@ export function Root() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Header label={state.label} hiveLabel={[state.swarm_name, state.hive_name].filter(Boolean).join(' / ') || null} pills={pills}>
|
||||
<Header
|
||||
label={state.label}
|
||||
hiveLabel={[state.swarm_name, state.hive_name].filter(Boolean).join(' / ') || null}
|
||||
nav={<MetaNav links={state.links} forgePublicUrl={state.forge_public_url} />}
|
||||
pills={pills}
|
||||
>
|
||||
<StatusChips
|
||||
aliveLabel={`${alive.glyph} ${alive.text}`}
|
||||
aliveTone={alive.tone}
|
||||
|
|
|
|||
Loading…
Reference in a new issue