frontend: trim narrated-history comments in packages/agent, packages/swarm-ui

Continues #3901. Same pattern as tabs.js/swarm.js: cut 'used to be
X, now Y'/'moved to'/'no longer' change-history narration down to
the current design fact, keep every load-bearing rationale intact
(the ResizeObserver feedback-loop note in Header.tsx, the Dropdown-
vs-real-<a> semantics in MetaNav.tsx, etc.).
This commit is contained in:
iris 2026-09-02 02:33:52 +02:00 committed by mara
commit c2733d2edf
6 changed files with 72 additions and 95 deletions

View file

@ -10,9 +10,9 @@
resolved as shared CSS rather than a JS component) see pill.css resolved as shared CSS rather than a JS component) see pill.css
itself for the classnames it covers. */ itself for the classnames it covers. */
@import "@hive/shared/pill.css"; @import "@hive/shared/pill.css";
/* Themed dialog/toast component CSS (modal.js) now lives in each /* Themed dialog/toast component CSS (modal.js) lives in each
component's own shadow root (adoptedStyleSheets), not a global component's own shadow root (adoptedStyleSheets), not a global
stylesheet nothing to @import here any more. */ stylesheet nothing to @import here. */
/* full-screen layout overrides /* full-screen layout overrides
The agent page mounts a full-viewport terminal under a fixed The agent page mounts a full-viewport terminal under a fixed
@ -587,9 +587,9 @@ pre.diff {
z-index: 35; z-index: 35;
} }
/* Composer chrome used to live inside `.terminal-wrap`; now lives /* Composer chrome lives inside the fixed `.agent-composer` defined
inside the fixed `.agent-composer` defined further up. The base further up. The base rules below stay scoped to whichever ancestor
rules below stay scoped to whichever ancestor owns it. */ owns it. */
.term-input .sendform-term { .term-input .sendform-term {
display: flex; display: flex;
/* mara, live: "bottom input row text not aligned / inconsistent /* mara, live: "bottom input row text not aligned / inconsistent
@ -639,31 +639,29 @@ pre.diff {
.term-input .submit-hint { color: var(--muted); font-size: 0.8em; flex: 0 0 auto; } .term-input .submit-hint { color: var(--muted); font-size: 0.8em; flex: 0 0 auto; }
.term-input.disabled .prompt { color: var(--muted); text-shadow: none; } .term-input.disabled .prompt { color: var(--muted); text-shadow: none; }
.term-input.disabled textarea { color: var(--muted); } .term-input.disabled textarea { color: var(--muted); }
/* Row + pill + details styling moved to hive-fr0nt::TERMINAL_CSS. */
/* side panel (singleton drawer) /* side panel (singleton drawer)
Inbox + loose-ends details open here instead of expanding inline. The Inbox + loose-ends details open here instead of expanding inline. The
drawer chrome itself (backdrop, drawer, resize handle, header, title, drawer chrome itself (backdrop, drawer, resize handle, header, title,
close button) is now the shared `<hive-side-panel>` element close button) is the shared `<hive-side-panel>` element
(@hive/shared/side-panel.js this also picks up drag-to-resize, (@hive/shared/side-panel.js, including drag-to-resize); what's left
which this page didn't have before); what's left here is the drag here is the drag cursor-override escape hatch (has to be a global,
cursor-override escape hatch (has to be a global, light-DOM rule it light-DOM rule it reaches every element on the page during a
reaches every element on the page during a drag, not just the shared drag, not just the shared element's own shadow tree) and the
element's own shadow tree) and the styling for the panel's own styling for the panel's own content types, which the shared
content types, which the shared element's shadow tree can never element's shadow tree can never reach (see
reach (see @hive/shared/side-panel/hive-side-panel.css's header @hive/shared/side-panel/hive-side-panel.css's header comment). The
comment). The `hive-side-panel .agent-inbox ` rules below use the `hive-side-panel .agent-inbox ` rules below use the element's own
element's own tag name as the selector root no compatibility class tag name as the selector root no compatibility class needed, the
needed, the tag name already uniquely identifies the light-DOM tag name already uniquely identifies the light-DOM instance app.js's
instance app.js's `sidePanel` creates. */ `sidePanel` creates. */
body.side-panel-resizing { body.side-panel-resizing {
user-select: none; user-select: none;
} }
body.side-panel-resizing * { cursor: ew-resize !important; } body.side-panel-resizing * { cursor: ew-resize !important; }
/* Inbox / loose-ends lists rendered into the side-panel body. The /* Inbox / loose-ends lists rendered into the side-panel body: strip
legacy <details>-collapsible variant of .agent-inbox is gone, so the inbox-only chrome (background, border-left) here and let the
here we strip the inbox-only chrome (background, border-left) and panel body's own padding own the framing. */
let the panel body's own padding own the framing. */
hive-side-panel .agent-inbox { hive-side-panel .agent-inbox {
margin: 0; margin: 0;
font-size: inherit; font-size: inherit;

View file

@ -1,12 +1,12 @@
// <Header> — the agent page's identity strip: icon, glyphic title, the // <Header> — the agent page's identity strip: icon, glyphic title, the
// "swarm / hive" label row, and a slot for the status row // "swarm / hive" label row, and a slot for the status row
// (`<StatusChips>` — ../status-chips/, kept separate so this component // (`<StatusChips>` — ../status-chips/, kept separate so this component
// has no opinion on what badges exist). Same three-column shape as the // has no opinion on what badges exist). Three columns: icon, main
// old `#agent-header` markup (icon · main · pills) — see the `pills` // (title + hive-label), pills — see the `pills` prop doc below for why
// prop doc below for what moved into that third column and why. // the third column is the one that grows. Reuses `agent.css`'s
// Reuses `agent.css`'s existing `.agent-header*`/`.agent-icon` rules // existing `.agent-header*`/`.agent-icon` rules (loaded globally by
// (loaded globally by index.html) rather than a component-scoped // index.html) rather than a component-scoped stylesheet — no new
// stylesheet — no new visual language needed for the chrome itself. // visual language needed for the chrome itself.
// Measures its own rendered height via ResizeObserver, writes it to // Measures its own rendered height via ResizeObserver, writes it to
// `--agent-header-real-h` (agent.css's downstream consumers — // `--agent-header-real-h` (agent.css's downstream consumers —
@ -42,23 +42,18 @@ export interface HeaderProps {
label: string; label: string;
hiveLabel?: string | null; hiveLabel?: string | null;
/** The status/badge row (`<StatusChips>`) lives in the /** The status/badge row (`<StatusChips>`) lives in the
* `.agent-header-pills` cluster now, alongside `pills` (see below), * `.agent-header-pills` cluster alongside `pills` (see below), not
* not stacked as its own row under the title. */ * stacked as its own row under the title. */
children?: ComponentChildren; children?: ComponentChildren;
/** Flyout triggers (inbox/todos/links pills, the overflow menu /** Flyout triggers (inbox/todos/links pills, the overflow menu
* button) and, since this round, `children` (the status badges) * button) plus `children` (the status badges), all in
* too, both in `.agent-header-pills`, the SAME header-level column * `.agent-header-pills`. `.agent-header-main` only ever holds the
* the old markup used for pills alone. First pass of this ask * title + hive-label rows, which don't wrap in practice and so stay
* nested pills into a row inside `.agent-header-main` instead * a stable height; only this pills-cluster column (already sized for
* backwards, mara corrected live: "i meant move badges to where the * a variable pill count) grows to fit badges+pills together, keeping
* dropdowns are, not the other way around. this way the height does * the header a stable height even on widescreen. Meta-nav
* not change even on widescreen" `.agent-header-main` now only * (`<MetaNav>`) lives here too, as a fixed-size trigger, not an
* ever holds the title + hive-label rows, which don't wrap in * inline link list in the title row (docs/web-ui/agent.md). */
* practice, so it stays a stable height; only this pills-cluster
* column (already used to absorbing a variable pill count) grows to
* fit badges+pills together. Meta-nav (`<MetaNav>`) lives in here
* too, as a fixed-size trigger not the old markup's inline list in
* the title row (`<nav id="meta-links">`, docs/web-ui/agent.md). */
pills?: ComponentChildren; pills?: ComponentChildren;
} }

View file

@ -1,38 +1,26 @@
// <MetaNav> — the header's meta-nav trigger: a single fixed-size Badge // <MetaNav> — the header's meta-nav trigger: a single fixed-size Badge
// (`@hive/shared/icons.js`'s `LinkIcon` — an emoji glyph here can't // (`@hive/shared/icons.js`'s `LinkIcon` — an emoji glyph can't match a
// match a neighbouring icon's size/weight on any platform, see that // neighbouring icon's size/weight on any platform, see that file's
// file's comment; mara: "agent page link icons is different from // comment) in the pills cluster that opens a popover listing
// swarm ui" was this trigger still on the old `🔗` emoji after
// swarm-ui's own link trigger had already moved to the SVG) in the
// pills cluster that opens a popover listing
// stats/screen/forge/config + any `hyperhive.dashboardLinks` extras, // stats/screen/forge/config + any `hyperhive.dashboardLinks` extras,
// sourced from the backend's `agent_links()` (the single source of // sourced from the backend's `agent_links()` (the single source of
// truth — same list also feeds the dashboard card's icon strip) — plus // truth — same list also feeds the dashboard card's icon strip) — plus
// a `🧭 dashboard` back-link, formerly the overflow menu's job (mara: // a `🧭 dashboard` back-link.
// "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).
// //
// `variant="quiet"` — mara: "link and settings button should not have // `variant="quiet"` — mara: "link and settings button should not have
// the badge bg". `Badge`'s default filled-pill look is right for a // the badge bg". `Badge`'s default filled-pill look is right for a
// status/picker chip, wrong for this icon-only trigger, which should // status/picker chip, wrong for this icon-only trigger, which should
// read as header chrome. See `@hive/shared/badge.js`'s `BadgeVariant`. // read as header chrome. See `@hive/shared/badge.js`'s `BadgeVariant`.
//
// v1 of this rendered every link inline in the header's title row — // A popover keeps the header height fixed — a variable-width inline
// mara, live: "the login card looks like it is behind the header" // link list would grow the title row past the fixed `--agent-header-h`
// (a variable-width link list can grow the title row past the fixed // the rest of the page is offset against (see Header.tsx's comment).
// `--agent-header-h` the rest of the page is offset against — see // Real `<a>` items, not `@hive/shared`'s `Dropdown` (its items are
// Header.tsx's comment) and "the links should have the same popout as // always `<button>`s for command dispatch, which would lose real link
// the links in the nav bar of swarm-ui." This is that: one quiet icon // semantics like ctrl/middle-click and "copy link address"), but
// trigger (reuses `@hive/shared`'s `Badge`, icon-only), a popover on // matching its `.ui-dropdown` visual values exactly (see MetaNav.css) —
// click, real `<a>` items — not `@hive/shared`'s `Dropdown` (its items // the same "reuse the values, not the component" call `LoginFlow`'s
// are always `<button>`s for command dispatch, which would lose real // `.login-card` makes for the same reason.
// 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 { useEffect, useRef, useState } from 'preact/hooks';
import { Badge } from '@hive/shared/badge.js'; import { Badge } from '@hive/shared/badge.js';
import { LinkIcon } from '@hive/shared/icons.js'; import { LinkIcon } from '@hive/shared/icons.js';
@ -73,8 +61,8 @@ export function MetaNav({ links, forgePublicUrl, dashboardBase }: MetaNavProps)
// entirely (never guessed from `<host>:3000`); `external` is already // entirely (never guessed from `<host>:3000`); `external` is already
// absolute; `container` is a same-origin path. // absolute; `container` is a same-origin path.
const visible = links.filter((lnk) => lnk.kind !== 'forge' || forgePublicUrl); const visible = links.filter((lnk) => lnk.kind !== 'forge' || forgePublicUrl);
// No early-return-on-empty any more — the dashboard link below is // No early-return-on-empty: the dashboard link below is always
// always present, so the trigger always has at least one item. // present, so the trigger always has at least one item.
return ( return (
<div class="meta-nav-anchor" ref={rootRef}> <div class="meta-nav-anchor" ref={rootRef}>

View file

@ -12,12 +12,11 @@
// calls — exactly why the bulk endpoint exists instead of looping the // calls — exactly why the bulk endpoint exists instead of looping the
// single-agent one. // single-agent one.
// //
// Owns the "+ agent" trigger too: creation used to be its own // Owns the "+ agent" trigger too — the roster this populates is the
// `/create-agent` route + nav item, but the roster this populates is // natural home for the action that populates it; a separate top-level
// the natural home for the action that populates it — a separate top- // nav entry would be one click of indirection for no benefit. The form
// level nav entry was one click of indirection for no benefit. The form // itself (`CreateAgentForm`) mounts inside a `Dialog` here rather than
// itself (`CreateAgentForm`) is unchanged from its page days, just // its own route.
// mounted inside a `Dialog` instead of a route.
import { useState } from 'preact/hooks'; import { useState } from 'preact/hooks';
import { ApiErrorPanel } from '@hive/shared/api-error-panel.js'; import { ApiErrorPanel } from '@hive/shared/api-error-panel.js';
import { readApiError, type ProblemDetails } from '@hive/shared/api-error.js'; import { readApiError, type ProblemDetails } from '@hive/shared/api-error.js';

View file

@ -179,10 +179,7 @@ export function CreateAgentForm() {
literal behaviour (deploying the container onto the hive isn't literal behaviour (deploying the container onto the hive isn't
wired up server-side yet see this file's top comment) wired up server-side yet see this file's top comment)
mara's explicit call on this copy: read as finished, not as a mara's explicit call on this copy: read as finished, not as a
running commentary on partial implementation. The 🪪 glyph that running commentary on partial implementation. */}
used to sit here as body copy now lives on `Panel`'s own `icon`
prop instead it's what prompted that prop to exist at all
(see Panel.tsx's doc comment) */}
<Panel title="what this creates" icon="🪪"> <Panel title="what this creates" icon="🪪">
<p> <p>
Submitting this queues everything a new agent needs: a swarm-level identity, a config Submitting this queues everything a new agent needs: a swarm-level identity, a config

View file

@ -16,16 +16,16 @@
// Page-switch animation, two pieces (motion-guard rules live in // Page-switch animation, two pieces (motion-guard rules live in
// Shell.css): `.shell-body` remounts on every navigation // Shell.css): `.shell-body` remounts on every navigation
// (`key={location}`) to replay a plain fade-in, and a single shared // (`key={location}`) to replay a plain fade-in, and a single shared
// underline (`.shell-nav-indicator`, replacing what used to be a // underline (`.shell-nav-indicator`) hops through every nav item it
// per-link border) hops through every nav item it passes over on its // passes over on its way to the new active one, not just a straight
// way to the new active one, not just a straight two-point tween — a // two-point tween — a nav hives→components jump visibly touches "new
// nav hives→components jump visibly touches "new agent" and "jobs" in // agent" and "jobs" in between, position and colour together. Accent
// between, position and colour together. Accent is a discrete cycle // is a discrete cycle through the existing base16 chromatic slots
// through the existing base16 chromatic slots (`NAV_ITEMS`' `accent` // (`NAV_ITEMS`' `accent` field), not a continuous hue rotation —
// field), not a continuous hue rotation — everything here still // everything here still derives from base16, same rule the rest of
// derives from base16, same rule the rest of the palette follows. // the palette follows. `.shell-brand` rides the identical accent value
// `.shell-brand` rides the identical accent value (`navAccent` below), // (`navAccent` below), so the header reads as one accent changing, not
// so the header reads as one accent changing, not the underline alone. // the underline alone.
import { useEffect, useRef, useState } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import type { ComponentChildren } from 'preact'; import type { ComponentChildren } from 'preact';
import { Link, useLocation } from 'wouter-preact'; import { Link, useLocation } from 'wouter-preact';
@ -109,10 +109,10 @@ function NavLink({
return ( return (
<Link href={href} className="shell-nav-link"> <Link href={href} className="shell-nav-link">
{/* Touch target (min-height) lives on the <a> so the whole row is {/* Touch target (min-height) lives on the <a> so the whole row is
tappable. The underline itself no longer lives here see tappable. The underline lives on the shared
`.shell-nav-indicator` this span is still what gets `.shell-nav-indicator`, not here this span is still what
measured to position that shared indicator, so it hugs just gets measured to position it, so it hugs just the text rather
the text rather than the full-height tappable box. */} than the full-height tappable box. */}
<span <span
ref={textRef} ref={textRef}
class={'shell-nav-link-text' + (active ? ' shell-nav-link-active' : '')} class={'shell-nav-link-text' + (active ? ' shell-nav-link-active' : '')}