diff --git a/docs/web-ui/agent.md b/docs/web-ui/agent.md index 899a9c3a..fb956086 100644 --- a/docs/web-ui/agent.md +++ b/docs/web-ui/agent.md @@ -69,29 +69,34 @@ structurally rather than for one specific trigger. Two columns: unprivileged process can't make directly). - **Inbox / todos pills** (`๐ฌ inbox ยท N` / `๐ todos ยท N`): hidden when empty; click opens the matching flyout in the side panel. - - **Links badge** (`๐`): opens a popover listing this agent's - `agent_links()`-sourced navigation (stats, screen when VNC is - enabled, forge profile + config-repo mirror when the agent has a - forge account, any `hyperhive.dashboardLinks` extras) as real - `` elements โ not a `Dropdown`-style command list, so - ctrl/middle-click and "copy link address" keep working. Each - `AgentLink.kind` resolves differently: `container` โ same-origin - path; `forge` โ `state.forge_public_url + url`, and the link is - dropped entirely when that's unset (never guessed from + - **Links badge** (`๐`): opens a popover listing `โ dashboard` + (back-link to the host dashboard, `${dashboardBase}dashboard.html`) + followed by this agent's `agent_links()`-sourced navigation (stats, + screen when VNC is enabled, forge profile + config-repo mirror when + the agent has a forge account, any `hyperhive.dashboardLinks` + extras) โ all as real `` elements, not a `Dropdown`-style + command list, so ctrl/middle-click and "copy link address" keep + working. Each `AgentLink.kind` resolves differently: `container` โ + same-origin path; `forge` โ `state.forge_public_url + url`, and the + link is dropped entirely when that's unset (never guessed from `:3000`); `external` โ already absolute. Same source (`GET /api/state`'s `links` field) also feeds `DashboardState.links` for the dashboard card's icon strip โ `agent_links()` in hive-agent is the single source of truth for - both. - - **Overflow badge** (`โฏ`): a `Dropdown` with two rows โ - `โ dashboard` (link) and `โป rebuild container` (select-twice - confirm, same action as the dashboard R3BU1LD button). Everything - else that used to live here (model/effort pickers, new-session, - logout) has a better home now: pickers are real badges above, and - `/new-session` / `/logout` are typed slash commands with their own - type-twice confirm (see below) โ a modal doesn't fit a text-input - flow, and burying rare-but-important actions in one flat menu was - the design guide's own named anti-example. + both. There is no separate overflow (`โฏ`) menu any more โ it used + to hold exactly this dashboard link plus a rebuild-container action + (mara, hyperhive#3704: "remove rebuild button, move link to + dashboards into links menu") โ rebuild had no real discoverability + need of its own (the dashboard's own R3BU1LD button already covers + it) so it's gone outright, and the dashboard link moved here, + leaving nothing to justify a separate menu. Everything else that + used to live in the old overflow menu (model/effort pickers, + new-session, logout) already had a better home before this: pickers + are real badges above, and `/new-session` / `/logout` are typed + slash commands with their own type-twice confirm (see below) โ a + modal doesn't fit a text-input flow, and burying rare-but-important + actions in one flat menu was the design guide's own named + anti-example. - No header cancel-turn button any more โ `/cancel` (slash command, below) is the only path; the turn-loop state badge already shows `thinking` as the discoverability cue. diff --git a/frontend/packages/agent/src/Root.tsx b/frontend/packages/agent/src/Root.tsx index baae05ed..d2124074 100644 --- a/frontend/packages/agent/src/Root.tsx +++ b/frontend/packages/agent/src/Root.tsx @@ -15,7 +15,6 @@ import { SidePanel } from './components/SidePanel.js'; import { InboxPanel } from './components/InboxPanel.js'; import { TodosPanel } from './components/TodosPanel.js'; import { TermInput } from './components/TermInput.js'; -import { OverflowMenu } from './components/OverflowMenu.js'; import { LoginFlow } from './components/LoginFlow.js'; import { useAgentState } from './hooks/useAgentState.js'; import { useTodos } from './hooks/useTodos.js'; @@ -89,11 +88,18 @@ export function Root() { <> setOpenPanel('inbox')} /> setOpenPanel('todos')} /> - {/* Needs a loaded snapshot for their data (links/forge_public_url, - * dashboard_port) โ both omitted (like the old page's - * populateOverflowMenu) until the first /api/state resolves. */} - {state ? : null} - {state ? : null} + {/* Needs a loaded snapshot for its data (links/forge_public_url, + * dashboard_port) โ omitted until the first /api/state resolves, + * same as the old page's populateOverflowMenu (now deleted โ + * dashboard-link moved here, rebuild removed outright, see + * MetaNav.tsx's file comment). */} + {state ? ( + + ) : null} > ); // Kept mounted regardless of `openPanel` (open/closed toggles just the diff --git a/frontend/packages/agent/src/components/MetaNav.css b/frontend/packages/agent/src/components/MetaNav.css index 3c957fa7..84e9d5c7 100644 --- a/frontend/packages/agent/src/components/MetaNav.css +++ b/frontend/packages/agent/src/components/MetaNav.css @@ -3,10 +3,8 @@ same "reuse the values, not the component" call `.login-card` (LoginFlow.css) already made, for the same reason: this popover's items are real `` tags, not `Dropdown`'s command-dispatch - ``s. Right-anchored, same as OverflowMenu.css's - `.header-overflow-anchor` โ MetaNav sits in the same right-hand - pills cluster, so a left anchor would push it off-screen the same - way. */ + ``s. Right-anchored โ MetaNav sits in the header's right-hand + pills cluster, so a left anchor would push it off-screen. */ .meta-nav-anchor { position: relative; display: inline-block; diff --git a/frontend/packages/agent/src/components/MetaNav.tsx b/frontend/packages/agent/src/components/MetaNav.tsx index 3792493a..985055e1 100644 --- a/frontend/packages/agent/src/components/MetaNav.tsx +++ b/frontend/packages/agent/src/components/MetaNav.tsx @@ -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 `` 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 ``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 `` items โ not `@hive/shared`'s `Dropdown` (its items +// are always ``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(null); @@ -58,13 +62,24 @@ export function MetaNav({ links, forgePublicUrl }: MetaNavProps) { // entirely (never guessed from `: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 ( setOpen((o) => !o)} expanded={open} /> {open ? ( + setOpen(false)} + > + โ dashboard + {visible.map((lnk) => { const href = lnk.kind === 'forge' ? `${forgePublicUrl}${lnk.url}` : lnk.url; return ( diff --git a/frontend/packages/agent/src/components/OverflowMenu.css b/frontend/packages/agent/src/components/OverflowMenu.css deleted file mode 100644 index 987f14ae..00000000 --- a/frontend/packages/agent/src/components/OverflowMenu.css +++ /dev/null @@ -1,19 +0,0 @@ -/* Anchors under the "โฏ" trigger โ same positioning - contract as StatusChips.css's `.status-chip-anchor`, kept as its own - rule here rather than reused across components (see this file's - sibling .tsx comment) so OverflowMenu doesn't depend on StatusChips' - incidental CSS. */ -.header-overflow-anchor { - position: relative; - display: inline-block; -} -/* @hive/shared's Dropdown.css anchors left-edge-to-left-edge, correct - for a picker with room to its right (StatusChips' model/effort). The - overflow trigger is the header's right-most element instead, so a - left anchor pushes the menu off-screen โ flip to right-edge-to- - right-edge here, scoped to this one consumer rather than changing - the shared default (which is still correct for every other caller). */ -.header-overflow-anchor .ui-dropdown { - left: auto; - right: 0; -} diff --git a/frontend/packages/agent/src/components/OverflowMenu.tsx b/frontend/packages/agent/src/components/OverflowMenu.tsx deleted file mode 100644 index d698cb53..00000000 --- a/frontend/packages/agent/src/components/OverflowMenu.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// โ the header's `โฏ` trigger. Deliberately trimmed vs. -// app.js's `populateOverflowMenu`: that one held dashboard-link/rebuild/ -// new-session/logout/model-picker/effort-picker all in one flat list โ -// the design guide's own named anti-example for a junk drawer. The -// model/effort pickers already moved to StatusChips' own Badge+Dropdown -// controls (this rewrite's first commit); new-session/logout now live -// as TermInput slash commands (typed, not menu-clicked, and already -// have their own two-step confirm). What's left here โ rebuild + a -// dashboard back-link โ are genuinely menu-shaped (rare, not tied to -// any other visible control), so they're still a `โฏ` menu, just a much -// shorter one. -// -// Reuses `@hive/shared`'s `Badge`+`Dropdown` โ same anchored-popover -// pair StatusChips' model/effort pickers use, not a bespoke popover. -// Rebuild's confirm is the same "select once to arm, select again to -// fire" pattern as TermInput's `/new-session`/`/logout` โ adapted here -// to a menu click instead of a typed repeat, but the same "requires -// deliberate repetition, not a modal" idea. -import { useState } from 'preact/hooks'; -import { Badge } from '@hive/shared/badge.js'; -import { Dropdown, type DropdownOption } from '@hive/shared/dropdown.js'; -import { submitRebuild } from '../lib/rebuildAction.js'; -import './OverflowMenu.css'; - -export interface OverflowMenuProps { - label: string; - dashboardBase: string; -} - -export function OverflowMenu({ label, dashboardBase }: OverflowMenuProps) { - const [open, setOpen] = useState(false); - const [armed, setArmed] = useState(false); - - function close() { - setOpen(false); - setArmed(false); - } - - const options: DropdownOption[] = [ - { value: 'dashboard', label: 'โ dashboard' }, - { value: 'rebuild', label: armed ? 'โป rebuild โ click again to confirm' : 'โป rebuild container' }, - ]; - - function onSelect(value: string) { - if (value === 'dashboard') { - window.open(`${dashboardBase}dashboard.html`, '_blank', 'noopener'); - close(); - return; - } - if (value === 'rebuild') { - if (armed) { - submitRebuild(dashboardBase, label); - close(); - } else { - setArmed(true); - } - } - } - - return ( - - setOpen((o) => !o)} expanded={open} /> - - - ); -} diff --git a/frontend/packages/agent/src/index.html b/frontend/packages/agent/src/index.html index de4f617a..ac4abb3d 100644 --- a/frontend/packages/agent/src/index.html +++ b/frontend/packages/agent/src/index.html @@ -9,7 +9,7 @@