mara, live, correcting the prior round: 'oh i meant move badges to
where the dropdowns are not the other way around. this way the height
does not change even on widescreen.'
Prior commit nested pills INTO a new row inside .agent-header-main
(badges + pills sharing a row under the title). Backwards from what
she wanted: badges now join .agent-header-pills instead — the same
header-level column pills already lived in, which already handles a
variable child count via flex-wrap + justify-content: flex-end.
.agent-header-main goes back to just the title + hive-label rows,
which don't wrap in practice, so its height stays stable regardless of
how many badges/pills are showing.
The ResizeObserver dynamic-height fix from the prior commit is
untouched and still does the real work of keeping .agent-status-
overlay's offset correct if anything does wrap — this is a pure
layout-preference change on top of that, not a new bugfix.
Verified at 500/1024/1400px — badges+pills form one right-aligned
cluster next to the title, no overlap, no ResizeObserver console
error. tsc --noEmit clean, build clean, both pre-push lints clean.
mara, live: 'login panel still looks like it is behind', 'bottom input
row text not aligned / inconsistent sizes', 'can we put the badges on
the same row as the links button etc? then the header would be more
compact.'
- Real fix for the overlap this time, not another band-aid: the
header's downstream offset (.agent-status-overlay, .agent-main's
scroll padding) was reading a STATIC --agent-header-h guess (6em).
Every prior fix on this PR removed one specific contributor to the
header wrapping past that guess (meta-nav's inline list, then
disconnected badge/pills clusters) but the guess itself was always
the actual defect — any content that can wrap an extra line breaks
it at whatever width triggers it, and no width is safe to promise.
Header.tsx now measures its own rendered height via ResizeObserver
and writes it to a new --agent-header-real-h var; downstream
consumers prefer that over the static guess. Deliberately a SEPARATE
var from --agent-header-h, not an overwrite: .agent-header's own
min-height also reads that var, so overwriting it in place created
an actual ResizeObserver feedback loop (confirmed live on the first
pass of this fix, fixed by splitting the var).
- Badges (StatusChips) + pills (inbox/todos/links/overflow) now share
one row (.agent-state-row) instead of two visually disconnected
clusters (old app.js markup's 3-column layout, which the new page
had also carried over) — pills pushed to the row's right edge via a
margin-left: auto rule scoped to that nesting, so app.js's own
still-live separate-column usage of the same classes is untouched.
- Composer text alignment: real cause was .prompt (1em) and
.submit-hint (0.8em) each getting a different-sized default line-box
under align-items: flex-start, so the same nominal padding-top
landed at different absolute offsets. align-items: center sidesteps
the whole line-box-size mismatch. This one's shared with app.js too
(same markup/classes) — same bug there, now fixed for both.
Verified at 480/500/768/1024/1400px — no overlap, no ResizeObserver
loop, badges+pills share one row, composer aligned. tsc --noEmit
clean, build clean, both pre-push lints clean.
mara, live review: 'the login card looks like it is behind the header',
'badges and interactive elements on same row have inconsistent size',
'the links should have the same popout as the links in the nav bar of
swarm-ui.' All three, addressed:
- Root cause of the overlap: MetaNav rendered every link inline in the
header's title row, a variable-width list that can grow the row past
the fixed --agent-header-h the rest of the page (position: fixed
header, position: absolute content below it) is offset against, so
the header visually covers whatever's underneath it once it's
actually taller than assumed. Fix: MetaNav is now a single
fixed-size trigger (reuses @hive/shared's Badge, icon-only, same
shape as the header's existing overflow trigger) in the pills
cluster, not a variable-width list in the title row. A single badge
can't grow the row regardless of how many links the backend sends.
Verified at 1024px/1400px (no overlap, was previously untested below
1400px) — the deeper fragility (StatusChips' badge row itself can
still wrap on a genuinely narrow/mobile viewport and hit the same
class of bug) is real but predates this PR and is out of scope here.
- The popover mara asked for: same visual language as swarm-ui's own
LinksMenu (Shell/LinksMenu.tsx) and this page's Dropdown/OverflowMenu
— quiet icon trigger, popover on click. Not built from @hive/shared's
Dropdown itself (its items are always <button>s for command dispatch,
which would lose real <a> link semantics — ctrl/middle-click, copy
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.
New Preact `SidePanel` drawer — deliberately not the shared
`<hive-side-panel>` shadow-DOM custom element, same rationale as
StatusChips' Badge/Dropdown. Drops that element's drag-to-resize +
localStorage width persistence for this first slice (fixed width via
CSS); reuses agent.css's existing `.agent-inbox`/`.inbox-*`/
`.side-panel-empty` content classes verbatim, only the drawer chrome
itself is new (SidePanel.css). Kept mounted regardless of open/closed
state (toggles the `open` prop) rather than conditionally rendered, so
the slide/fade CSS transitions actually fire on both open and close.
- InboxPanel: renders `state.inbox` (already polled by useAgentState)
+ a "mark all read" action — plain `fetch()` to the dashboard's
`/api/agent/{name}/mark-all-read` (JSON response, same as app.js;
unlike pause/resume this one doesn't need a form-submit workaround).
- TodosPanel: new `useTodos` hook (same 4s poll cadence as app.js's
`refreshTodos`) + bulk select/mark-done via `api/todos/mark-done`.
- Both destructive-but-recoverable actions use a new `useConfirmClick`
two-click-arm hook instead of the old shadow-DOM `themedConfirm`
modal — lighter weight, no backdrop/dialog machinery needed for a
"did you mean to click that" nudge.
- HeaderPill: the inbox/todos count triggers in the header's right
cluster; Header.tsx gained a `pills` slot for them (mirrors the old
markup's `.agent-header-pills` third column).
- types.ts: added `InboxRow`/`TodoRow`, mirroring
`hive_sh4re::inbox::{InboxRow, LooseEnd}` (todo variant only —
`/api/todos` never returns the others).
Screenshot-verified against a mock server exercising both panels.
Adds a new Preact/TSX build alongside the existing app.js (esbuild
entry `main.tsx` → dist/static/main.{js,css}, same jsx/tsconfig shape
swarm-ui already uses) and the first real page slice: Header +
StatusChips, composing the Badge/Dropdown components from the prior
commit's PR. Not wired into index.html yet — app.js keeps rendering
the live page untouched while this fills in component by component
(state polling, the live SSE stream, login flow, inbox/todos, term
input) in follow-up commits on this branch.
StatusChips folds the model/effort pickers and pause into the badge
row itself (each badge IS its own control), replacing the old
overflow-menu-only pickers — the concrete fix the design guide already
names this page as needing. Presentational only for now (props, not
live data) so it's reviewable against sample data before being wired
to /api/state.
Screenshot-verified against the real agent.css/theme.css/colors.css
(headless chromium, sample data) — renders correctly.
Builds + tsc --noEmit clean.