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:
parent
8e97fc6263
commit
668ccc2278
8 changed files with 71 additions and 143 deletions
|
|
@ -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
|
||||
`<a>` 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 `<a>` 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
|
||||
`<host>: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.
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<>
|
||||
<HeaderPill kind="inbox" icon="📬" label="inbox" count={state?.inbox.length ?? 0} onClick={() => setOpenPanel('inbox')} />
|
||||
<HeaderPill kind="todos" icon="📋" label="todos" count={todos.length} onClick={() => 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 ? <MetaNav links={state.links} forgePublicUrl={state.forge_public_url} /> : null}
|
||||
{state ? <OverflowMenu label={state.label} dashboardBase={resolveDashboardBase(state.dashboard_port)} /> : 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 ? (
|
||||
<MetaNav
|
||||
links={state.links}
|
||||
forgePublicUrl={state.forge_public_url}
|
||||
dashboardBase={resolveDashboardBase(state.dashboard_port)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
// Kept mounted regardless of `openPanel` (open/closed toggles just the
|
||||
|
|
|
|||
|
|
@ -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 `<a>` tags, not `Dropdown`'s command-dispatch
|
||||
`<button>`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. */
|
||||
`<button>`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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
/* Anchors <Dropdown> 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;
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// <OverflowMenu> — 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 (
|
||||
<div class="header-overflow-anchor">
|
||||
<Badge value="⋯" title="more actions" onClick={() => setOpen((o) => !o)} expanded={open} />
|
||||
<Dropdown open={open} options={options} label="more actions" onSelect={onSelect} onClose={close} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<link rel="stylesheet" href="static/theme.css">
|
||||
<link rel="stylesheet" href="static/agent.css">
|
||||
<!-- main.css (bundled component CSS: Badge/Dropdown/LoginFlow/MetaNav/
|
||||
StatusChips/OverflowMenu/SidePanel/...) MUST load after agent.css —
|
||||
StatusChips/SidePanel/...) MUST load after agent.css —
|
||||
`.login-card`/`.meta-nav-popover`/etc. share class-selector
|
||||
specificity with agent.css's legacy rules, so load order is what
|
||||
decides which one wins the cascade (see LoginFlow.css/MetaNav.css
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
// Rebuild POST to the *dashboard's* origin — same cross-origin-needs-a-
|
||||
// real-form-submit reasoning as pauseAction.ts's `submitPauseResume`
|
||||
// (hive-c0re's `/api/rebuild/{name}` returns a plain 200, no CORS
|
||||
// header, so a cross-origin `fetch` can't read the result).
|
||||
export function submitRebuild(dashboardBase: string, label: string): void {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = `${dashboardBase}api/rebuild/${label}`;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
Loading…
Reference in a new issue