Commit graph hyperhive/frontend/packages/agent/src
Author SHA1 Message Date
iris
e1e5195081 dashboard + agent UI: surface hive_name + swarm_name in chrome (#701)
Damocles's backend (c41bf1b) landed HYPERHIVE_HIVE_NAME / _SWARM_NAME
env vars and identity.rs accessors. This commit wires them into the two
web surfaces:

hive-c0re/dashboard.rs:
  StateSnapshot gains `hive_name` + `swarm_name` (Option<String>),
  populated from the env vars the c0re NixOS module injects.

hive-ag3nt/web_ui.rs:
  Per-agent StateSnapshot gains the same two fields, populated from
  crate::identity::hive_name() / swarm_name().

Dashboard frontend (index.html + tabs.js):
  A `<p class="banner-thin" id="hive-identity">` sits above the tab
  strip in the chrome. refreshState() populates it with "swarm / hive"
  (or just "hive") when the fields are non-null, and updates
  document.title to "<swarm>/<hive> // h1ve-c0re". No change to
  chrome when both fields are null (backward compat).

Per-agent frontend (index.html + app.js + agent.css):
  A hidden `.agent-hive-label` span under the title row shows the
  swarm/hive label once setHeader() receives non-null names.
  document.title is updated to "<label> // <hive_name>" when hive_name
  is set, enabling browser-tab disambiguation when multiple hives are
  open in parallel. Styled as a small uppercase purple-dim sub-label.
2026-05-31 21:09:08 +02:00
iris
4cee50a3be agent/app.js: final cookie scrub — 0 cookies (#713 batch 3 final)
All 19 remaining cookies in app.js were attribution refs to
closed issues / PRs / past mara/argus reviews. Substance is
already in docs from earlier batches (PR #780). This batch
collapses the cookie attributions to brief inline notes pointing
at docs where appropriate.

- #360 side-panel duplication note → drop closed-issue framing
- #394 vibec0re header redesign (×5) → drop cookies, prose stays
- #589 hive-qualified label glyphic-title note → drop cookie
- #576/#542/#584 logout chain rationale → drop cookies, prose
  preserved (the WHY + .credentials.json + mcp-needs-auth-
  cache.json detail stays inline)
- #14 relative paths convention → docs/web-ui.md::Per-agent
  relative paths pointer
- #233 marked autolinks new-tab → drop cookie
- #559/#566 mark-all-read flyout (×3) → docs/web-ui.md::Per-agent
  page (Loose-ends flyout) pointer + drop attribution
- #666 ask→operator inline-answer slot (×2) → docs/web-ui.md::
  Per-agent page (Ask → operator inline-answer binding) pointer

app.js: 19 → 0 cookies (100% reduction). Combined with PR #780
+ batch 1 + 2, app.js is fully migrated for #713 scope.

Closes #713 — all agent UI files (app.js, agent.css, screen.html,
index.html, stats.js, stats.html) at 0 issue-ref cookies.
2026-05-31 15:36:23 +02:00
iris
dfbaa2654b agent/index.html: collapse header comments to docs pointers (#713 batch 3 cont.)
3 #NNN cookies scrubbed from inline HTML comments. The header
two-row layout (#394) and the overflow-extra-click rationale
already live in docs/web-ui.md::Per-agent page (after PRs #780
and #781 batches 1-2). Each comment shrinks to a doc-pointer.

index.html: 3 → 0 #NNN cookies (100% reduction).
2026-05-31 14:47:32 +02:00
iris
815f6561b7 screen.html + docs: migrate canvas-sizing + RFB design prose (#713 batch 3)
Expanded the /screen endpoint description in docs/web-ui.md to
cover the substantive design rationale that lived in screen.html
comments. screen.html shrinks correspondingly.

Moved to docs/web-ui.md::Per-agent endpoints (GET /screen):
- Deliberate thinness — minimal RFB renderer; noVNC vendoring
  path called out for production-grade replacement.
- Fit-mode flex-item min-width:auto clamp: a flex item's automatic
  minimum size resolves to the canvas's intrinsic framebuffer
  resolution and clamps CSS max-* back up, making fit mode a
  silent no-op. The fix (flex: none + min-width: 0 + min-height: 0
  + explicit px sizing via relayoutCanvas()) is now documented.
- localStorage persistence for fit-mode (key screen-fit; default
  on).
- Pointer rescale in sendPointer keeps clicks accurate.
- ExtendedDesktopSize pseudo-encoding (-308 rect) gates the
  match-size button.

Collapsed in screen.html: 8 #NNN cookies scrubbed across all
inline comments. #133 (canvas-sizing fit bug — closed) ×5,
#52 (noVNC vendoring — closed) ×1, #14 (relative URL — closed
my piece) ×1, plus one CSS-block #133. Each comment shrinks to
a brief pointer.

screen.html: 8 → 0 #NNN cookies (100% reduction). Net ~26 lines
of substantive prose moved into docs/web-ui.md.
2026-05-31 14:47:32 +02:00
iris
b07d74b51b agent.css + docs: migrate icon sizing + popover :not([hidden]) prose (#713 batch 2)
agent.css carried 17 #NNN cookies, mostly attribution refs to the
#394 vibec0re overhaul (mara's full-screen redesign). Most were
not substantive WHY-prose — just "this exists because of #394"
breadcrumbs. The two genuine WHY-explanations move to docs.

Moved to docs/web-ui.md::Per-agent page:
- **Agent icon paragraph**: rewrote with explicit-em-sizing
  rationale (intrinsic <img> dimensions push parent flex container
  open via align-items: stretch height feedback) + the 5em ≈ 6em
  min-height - 0.5em padding × 2 derivation + align-self:
  flex-start sticks-to-top.
- **Overflow button paragraph**: added the :not([hidden]) display
  scoping rationale (UA stylesheet sets display:none on [hidden],
  but author display:flex would override — scope to :not([hidden])
  so the popover stays hidden until JS unhides).

Collapsed in agent.css: 17 cookies scrubbed across the file:
- #360 (full-screen vibec0re overhaul — closed) ×2: section
  header preface + side-panel section header
- #394 (vibec0re header redesign — closed) ×7: header height,
  main column, agent icon, meta-nav, overflow trigger, two
  orphaned-style tombstones
- #411 (popover scoping + icon align-start — closed) ×2: icon
  sticks-to-top + overflow popover scoping
- #568 (OAuth code mask + reveal — closed) ×1: show/hide toggle
- #666 (ask→operator inline-answer slot — closed) ×1: slot
  styling (substance partly in PR #780 docs section)
- #559 (mark all read header row — closed) ×1
- #376 (inbox row layout — closed) ×1: long-message wrap
- #375 (tail pill z-index — closed) ×1: collision fix

agent.css: 17 → 0 refs (100% reduction, fully migrated).
2026-05-31 14:47:32 +02:00
iris
3f84b6492a agent/app.js + docs: migrate ask→operator inline-answer binding prose (#713 batch 1 cont.)
Moves the #666 ask→operator inline-answer wiring rationale from
app.js into a new docs/web-ui.md::Per-agent page sub-paragraph
**Ask → operator inline-answer binding**. Substantial block —
the slot-registry / reconcileAskBinds / buildAnswerForm trio has
real design rationale (async question id, text-match pairing,
resolved-vs-cancelled-vs-expired neutrality, defensive prune,
no-regression-fallback to side-panel answer) that belongs in
docs, not three JSDoc blocks scattered through app.js.

Moved to docs (~30 lines of substantive prose):
- pendingAskBinds slot-registry rationale
- reconcileAskBinds matching algorithm (text-match, first-unbound
  to first-unclaimed pairing stability)
- defensive prune for disconnected slots
- [resolved] vs ✓ rationale (answered / cancelled / TTL-expired
  ambiguity — neutral label)
- fallback to side-panel answer when slot stays unbound

Collapsed in app.js: 3 JSDoc blocks + inline comments → ~10-line
total pointer. Drops #666 ×3, #559, #668 cookies en passant
(substance now lives in docs).

app.js: 24 → 19 refs in this commit; 27 → 19 across batch 1
(30% reduction so far). Net ~57 lines of substantive prose
migrated from app.js to docs/web-ui.md across both commits in
this PR.
2026-05-31 14:35:09 +02:00
iris
8366c3739b agent/app.js + docs: migrate stacking-context + OAuth + nav-link prose to docs (#713 batch 1)
First batch of #713 (agent per-container UI prose migration).
Moves 3 substantive WHY-explanations from app.js into
docs/web-ui.md::Per-agent page, replaces each in-code mention
with a brief pointer.

Moved to docs:
- **Terminal-wrap pill anchor** (~11 lines): why the `↓ N new`
  pill is anchored in `.agent-main` rather than the default
  `.terminal-wrap` parent — backdrop-filter creates a stacking
  context, anchoring inside it traps the pill's z-index below the
  composer.
- **OAuth code input** (~14 lines across two blocks): masked
  password + reveal toggle + `autocomplete="one-time-code"`
  rationale (shoulder-surfer / screenshot exposure + WHATWG
  semantic value + suppresses browser save-password prompt).
- **NavLink kind resolution + XSS-safe DOM-build** (~8 lines):
  Container → same-origin, Forge → `http://<host>:3000<url>`,
  External → already absolute; agent-declared strings never reach
  innerHTML.

Collapsed in app.js: each block becomes a 3-4 line pointer to the
matching docs section. Drops #262 / #568 / #592 cookies en passant
since their substance now lives in docs. The `#14` cookie on
`historyUrl` / `streamUrl` is also scrubbed — the path-relative
shape is the convention, not an open issue. `#375` (agent.css ID
ref) drops as part of the pill-anchor block collapse since it was
sole-purpose pointing inside that comment.

app.js: 27 → 24 refs. Net ~35 lines of substantive prose moved out
of app.js into docs/web-ui.md::Per-agent page, where it belongs.
2026-05-31 14:35:09 +02:00
iris
37d99ed118 agent UI: relative paths for all assets/api/ws so the page works under any nginx prefix (#14)
Per mara on #14: 'make agent page not assume root path, links / api
calls need to be relative'. atlas's nginx side (#15) will mount the
per-agent UI at a prefix like /agent/<name>/ instead of its own
port; for the page to keep working under that prefix, every
in-page reference needs to resolve document-relative rather than
root-anchored.

Converted in this pass:
- HTML <link>/<script>/<img>/<a> hrefs in index.html, stats.html,
  screen.html: '/icon' → 'icon', '/static/agent.css' →
  'static/agent.css', back links '/' → './'.
- app.js fetch() targets ('/api/state' → 'api/state', /api/cancel,
  /api/loose-ends, etc.), form actions ('/login/start', '/send'),
  EventSource urls ('/events/stream', '/events/history').
- stats.js fetch() targets.
- screen.html WebSocket URL: was hardcoded as
  ws(s)://host/screen/ws; now derived from document.baseURI via
  new URL('screen/ws', document.baseURI) so the gateway prefix
  flows through.

Slash-command labels (/cancel, /compact, …) and the dashboard-port
link (different port, intentionally absolute) intentionally
untouched.

Added a new 'Per-agent relative paths' section to docs/web-ui.md
covering the rationale + the trailing-slash gotcha (sub-pages like
/stats must NOT have a trailing slash, or 'static/app.js' resolves
under /stats/ instead of replacing the segment).

Functional code unchanged; build clean. Damocles + atlas can
proceed with the backend / nginx side without depending on this
landing first, but once both ship the agent page works under the
gateway-prefixed URL without further changes.

refs #14
2026-05-31 12:10:56 +02:00
iris
a9114342e4 agent/terminal: neutral [resolved] label for closed asks (argus #668)
Loose-ends endpoint only carries pending state — a question
disappearing from the list can mean answered, cancelled by asker,
or TTL expired. Previous [answered ✓] glyph implied successful
operator response across all three paths.

Rename .ask-answered-tag → .ask-resolved-tag and use the neutral
[resolved] label. Full resolution detail (who answered with what)
remains visible via the question's history in the side panel.

Addresses argus review note on #668.
2026-05-30 21:45:33 +02:00
iris
e056e82d6d agent/terminal: inline answer form under ask → operator rows (#666)
When an agent calls mcp__hyperhive__ask with to==operator (the
default), the rich tool-use renderer now mounts an empty
ask-answer-inline-slot inside the expanded ask row and enqueues a
loose-ends refresh. A new reconcileAskBinds() walks waiting slots
on every loose-ends update, matches each against pending
operator-bound questions by question text, and injects the same
inline answer form the side panel uses (buildAnswerForm → POST to
the host dashboard answer-question endpoint). When a question
resolves, the form gets replaced by a struck-through [answered ✓]
tag so the scrollback reflects the closed state.

Lets the operator respond to agent questions inline in the live
terminal without context-switching to the loose-ends side panel or
the dashboard Y3R C4LL tab.

Refreshes loose-ends both on tool_use render (best-effort, may
miss the question before the broker persists it) and on the
matching tool_result (the right moment — MCP has just returned the
assigned id). Slots are pruned on /clear and defensively filtered
for isConnected on each reconcile.

closes #666
2026-05-30 21:45:33 +02:00
iris
f44bf19707 hive-ag3nt: identity module with hive-qualified label (#589 phase A, first PR)
First chunk of #589 v0 phase A: plumbing the hive-qualified
'name@hive' form through the per-agent surfaces that the harness
itself owns. Broker from/to + dashboard rendering + container_view
follow in subsequent PRs once damocles ships the HYPERHIVE_HIVE_DOMAIN
env var in harness-base.nix.

- new hive_ag3nt::identity module: label() / hive_domain() /
  qualified_label() / qualify(label). Reads HYPERHIVE_HIVE_DOMAIN
  (set by hive-c0re.nix module from hyperhive.domain) — when unset
  or empty, qualified_label degrades to just the short label so
  existing single-hive deployments are unchanged. Six unit tests
  cover the set / unset / empty / arbitrary-label paths.
- prompt::render gains {qualified_label} substitution alongside
  the existing {label}. system.md template uses both: the agent
  intro now reads 'You are hyperhive agent iris (qualified:
  iris@darkest.space) in a multi-agent system. ... When you're
  talking to or about a peer on a different hive, use the
  qualified form (name@hive) so the operator + the manager can
  disambiguate'. Manager flavor gets the same treatment.
- /api/state gains qualified_label: String. Always present, equals
  label when no domain is configured.
- frontend setHeader takes the qualified_label, drives the browser
  tab title (so two  tabs from different hives are
  distinguishable in the tab bar) while the glyphic #title stays
  short for the cinematic header.

Gated on env var presence — no behaviour change for single-hive
deployments. Pairs with damocles's upcoming harness-base.nix
HYPERHIVE_HIVE_DOMAIN ship; safe to land in either order.
2026-05-29 19:09:44 +02:00
iris
944943b7df agent: autocomplete='one-time-code' on OAuth input (argus #592 nit)
Browsers may silently ignore autocomplete='off' on type='password'
inputs (UA override to help users save credentials). For an OAuth
code, the semantic value is 'one-time-code' which is honoured + has
the side benefit of suppressing the 'save password?' prompt that
would otherwise fire on form submit.
2026-05-29 18:40:54 +02:00
iris
3b597ea028 agent: mask OAuth code input + reveal toggle on login screen (closes #568)
The login-in-progress screen's OAuth-code input was a plain text
field — anyone shoulder-surfing or capturing a screenshot of the
agent web UI would see the code in cleartext. Same risk applies
to dashboard share-screens during live demos.

Changes:

- input switches to type='password' so the pasted code renders
  as bullets by default. Placeholder updated to '(hidden)' so the
  operator knows the masking is intentional, not a browser quirk.
- new 'reveal' button (👁) next to the input flips the type back
  to text on press, so the operator can sanity-check the paste
  before submitting if she wants. aria-pressed reflects state.
- CSS for the reveal button mirrors the existing .btn-login amber
  family — quiet by default, amber border/glow when pressed.
- spellcheck='false' on the input so browsers don't try to
  underline the random-looking string as a typo.

The on-screen OAuth URL stays visible (the operator needs to
click it). The code is the secret leg — only the operator's
browser holds it, the URL is what was posted publicly to claude's
OAuth provider.
2026-05-29 18:38:17 +02:00
iris
5f8bc5315a agent: logout dialog now reflects #585's narrowed wipe (OAuth-only, session preserved)
After PR #585 (closes #584) narrowed the backend wipe to just
.credentials.json + mcp-needs-auth-cache.json (preserving
projects/<hash>/*.jsonl session history + everything else under
~/.claude/), the dialog wording I shipped in 9d58ec3 (which
described a full-dir wipe to satisfy argus's review against the
PRE-#585 backend) is now stale.

Updated to match what the backend actually does:

- title + slash desc + both confirm dialogs now say 'rotate OAuth
  credentials, --continue session history preserved'
- confirms explicitly name the two files deleted (.credentials.json,
  mcp-needs-auth-cache.json) so the operator knows exactly what's
  going + reassures that the conversation context survives
- 'agent picks up where it left off on the next turn after re-login'
  tail makes the recoverability concrete

No code-path changes — just the wording. Backend behavior + endpoint
shape unchanged.
2026-05-29 17:35:38 +02:00
iris
9d58ec391b agent: logout confirm dialog accurate about --continue session loss (#576 argus nit)
argus #583 review caught: the prior dialog wording said 'prior
--continue context is not affected (only the OAuth creds)', but
that's false — paths::claude_dir() is /root/.claude and
remove_dir_all wipes the projects/<hash>/*.jsonl session history
along with the OAuth creds.

Updated both the overflow-menu confirm and the /logout slash
command confirm to explicitly say the projects/*.jsonl session
history (--continue context) goes too, plus a 'no undo' tail so
the operator can't read past the consequence list. SLASH_COMMANDS
desc + overflow-item title also corrected so /help + tooltips
match.
2026-05-29 16:56:47 +02:00
iris
259b236ad1 agent: 🔓 logout in overflow menu + /logout slash command (#576 frontend half)
Pairs with damocles PR #582 (POST /api/logout backend on the
per-agent web UI). Three additions to the agent's app.js:

- New `postLogout` helper next to postCancelTurn / postCompact /
  postNewSession. Same postSimple shape.
- New entry in SLASH_COMMANDS so /help lists /logout.
- New /logout case in handleSlashCommand with window.confirm.
- New '🔓 logout' item in populateOverflowMenu's overflow popover,
  mirroring the new-session item's pattern (confirm before POST,
  disable button while in-flight, closeOverflowMenu before fire).

Confirm dialog spells out the consequences (SIGINT, creds wiped,
park in needs-login) so the operator doesn't accidentally drop a
production session. Tooltip on the menu item links the action back
to the credentials directory + post-logout state.

Wire-level: POST /api/logout, no body. Backend returns 200 with a
text body describing the wipe outcome — postSimple ignores it
(success → no terminal note; failure → red turn-end-fail row).
2026-05-29 16:56:47 +02:00
iris
fe2f9c2396 agent: "mark all read" button on the inbox flyout (#559 frontend half)
Pairs with damocles PR #566 (broker primitive + dashboard
`POST /api/agent/{name}/mark-all-read` route). The agent's
per-container inbox side-panel now gets a header row with a
`✓ mark all read` button that:

- confirms via a one-line dialog (the action is destructive: any
  pending broker message for this agent is acked, the harness
  won't receive a wake-prompt for them)
- POSTs to the host dashboard (cross-origin, same pattern as the
  existing operator-answer flow on this page)
- surfaces `{ marked: N }` in an inline status pill, then triggers
  a `refreshState` so any state-derived surfaces re-read fresh
- stays out of the way when the inbox is empty (only renders above
  a non-empty rows list)

Note: `recent_for` returns the most-recent-N messages regardless of
ack state, so clicking does NOT visually empty the rows list. The
status pill ("✓ marked N as read") is the operator-facing
confirmation; the next `turn_start` will show `0 unread` in its
badge. Tooltip on the button calls this out so the operator isn't
surprised the row list stays put.

CSS mirrors the existing answer-form button family (mauve hover on
bg-elev background) so it reads as a peer affordance, with a
border-bottom separating it from the message list.
2026-05-29 13:14:06 +02:00
damocles
dca62fda40 harness: remove deprecated manager-side request_spawn surface (closes #442) 2026-05-27 18:58:04 +02:00
iris
cf4fc5ce1c agent: cap icon at 5em, fix overflow menu hidden-state (#411)
Two regressions from #394 that landed once mara deployed:

1. **Icon way too big** — `.agent-icon { height: 100%; aspect-ratio: 1 }`
   sized off the parent's height. With `align-items: stretch` on the
   header and `min-height: 6em`, the `<img>`'s intrinsic dimensions
   fed back into the flex container's height calculation and pushed
   the header (and the icon stretched to fill it) far past 6em — the
   icon ended up dominating the page.

   Switch to explicit `width: 5em; height: 5em` (header content area
   = 6em min-h - 2 × 0.5em padding). `align-self: flex-start` so a
   state-row wrap doesn't drag the icon down with it.

2. **Overflow menu always visible** — `.overflow-menu { display: flex }`
   in author CSS overrode the `[hidden]` UA rule (`[hidden]
   { display: none }` has the same specificity). With nothing hiding
   it, the menu rendered at viewport 0,0 by default (no top/left set
   on the popover until JS opens it), so the operator saw `↑ dashboard
   / ↻ rebuild container / ↻ new claude session` stacked at the
   top-left of every page load.

   Split the rule: `.overflow-menu` keeps the chrome (frosted bg,
   position fixed, z-index, border, padding); `.overflow-menu:not([hidden])`
   carries `display: flex`. Now `[hidden]` wins when set, no menu
   shown until the trigger opens it.

agent.css 22.4kb → 22.5kb.
2026-05-25 02:19:47 +02:00
iris
92becdd951 agent: drop orphaned button rules in agent.css (#394 follow-up)
After moving `↑ DASHB04RD` / `↻ R3BU1LD` / `↻ new session` into
the overflow `⋯` menu in #394, the per-button rules in agent.css
have no live consumer:

- `.btn-dashlink` (cyan back-link chip)
- `.btn-rebuild` (amber rebuild chip — note: the dashboard has
  its own `.btn-rebuild` for the per-row R3BU1LD form on the
  SW4RM tab, that stays)
- `.btn-new-session` (amber round-pill button)
- `.btn-send` (green send-button variant — orphaned since an
  earlier compose-form retirement, swept here)

Plus a comment-only `#state-row` block (layout fully provided by
the renamed `.agent-state-row` / `.agent-header-row` classes).

Each removed block is replaced with a one-line "moved to overflow"
comment so future readers don't reintroduce the old chrome by
mistake.

agent.css 23.6kb → 22.4kb.
2026-05-25 01:29:39 +02:00
iris
69312b8553 agent: redesign terminal header — full-height icon, two-row main, overflow menu (#394)
Operator brief (#394): the header had thirteen distinct visual
elements in one flex row with three different border-radius
languages, four colour treatments, three label styles. Mara's
direction:

- agent icon bigger (full header height) as the identity anchor
- title glow stays
- nav links lose their default-anchor underline
- overflow `⋯` absorbs `↻ R3BU1LD` + `↻ new session` (rare,
  destructive — both worth one extra click; rebuild is normally
  done from the dashboard)
- accent stacking in the state strip stays — that's the vibe

## Layout shape

Three flex columns in `.agent-header`:

  [icon · full height] [main column · 2 rows] [pills + ⋯]

The main column carries row 1 (`◆ AGENT ◆` title + meta-nav) on
top and row 2 (alive · state · model · ctx · cost · last-turn ·
cancel-turn) below.

## Changes

### `index.html`
- Wrap title + nav in `.agent-header-row .agent-header-title-row`;
  wrap state-row siblings in `.agent-header-row .agent-state-row`;
  both go inside a new `.agent-header-main` column.
- Right cluster `.agent-header-pills` contains the inbox + loose
  pills + a new `<button id="overflow-btn">⋯</button>` trigger.
- Drop static `#new-session-btn` from `#state-row` — moved into the
  overflow menu, populated dynamically.
- Add `<div id="overflow-menu" role="menu" hidden>` as a sibling
  of `<header>` (lives outside the header so its `position: fixed`
  popover isn't trapped by any header stacking context).

### `agent.css`
- `--agent-header-h: 4.6em → 6em` so the icon can be square + full
  height without crowding the two-row main column. Terminal
  padding-top + status overlay top + tail-pill all derive from
  this variable, so they follow automatically.
- `.agent-header { align-items: stretch }` lets the icon stretch
  to full height; `.agent-icon { height: 100%; aspect-ratio: 1 }`
  sizes it as a square off the stretched height.
- `.agent-nav-link` rule added — `text-decoration: none`, cyan +
  soft glow, hover lights brighter (mara's spec).
- `.overflow-btn` (round trigger) + `.overflow-menu` (frosted
  popover, fixed-position) + `.overflow-item` (rows with an icon
  column + label, hover ink matches per-action accent — cyan for
  dashboard, amber for rebuild/new-session).
- Remove the old `#state-row` selector (layout now provided by
  `.agent-state-row` + `.agent-header-row`).

### `app.js`
- `setHeader` no longer appends DASHB04RD / R3BU1LD chips into the
  title — title is just the identity glyph now. Both actions get
  rendered into the overflow menu by `populateOverflowMenu()`.
- `populateOverflowMenu(label, dashUrl)` builds three rows:
  `↑ dashboard` (anchor), `↻ rebuild container` (button — same
  POST-form action as before), `↻ new claude session` (button —
  same `/api/new-session` call as the legacy header button).
- Overflow toggle / outside-click / Escape dismissal — same
  pattern as the side-panel flyout (`Panel`).
- Drop the static `new-session-btn` IIFE binder; the dynamically-
  rendered menu item owns its handler now.
- Drop the per-nav-link inline `marginLeft` (layout gap comes from
  the new `.agent-nav { gap }` rule).

## Validation

- `npm run build` clean.
- Build deltas: agent.css 21.0kb → 23.6kb (overflow + nav rules
  + comments), app.js 117.4kb → 118.9kb (menu builder + toggle).
- Browser smoke test isn't possible from inside iris's container.
  Worth eyeballing post-deploy:
    - Icon fills the full header height as a square
    - Title glow + uppercase styling preserved
    - Nav links render without underline; hover lights brighter
    - `⋯` opens a frosted popover with `↑ dashboard`, `↻ rebuild
      container`, `↻ new claude session`
    - Rebuild confirm + POST works the same as the legacy chip
    - New-session confirm + POST works the same as the legacy button
    - State strip still wraps when crowded (model/ctx/cost
      multi-line on narrow viewports)
    - Cancel-turn button still appears while thinking and clears
      on turn end
    - Terminal padding-top adjusts to the new 6em header height
      (no row hidden under the chrome)
2026-05-25 01:29:39 +02:00
iris
47403595f1 terminal: anchor tail pill outside .terminal-wrap stacking context (#375)
#375 set `z-index: 35` on the agent page's tail pill so it'd float
above the fixed composer (z-index 30). The fix only landed because
nothing else in `.agent-main` created a stacking context. But the
pill is anchored inside `.terminal-wrap`, which carries
`backdrop-filter: blur(...) saturate(...)` for the frost effect —
and `backdrop-filter` CREATES A STACKING CONTEXT. The pill's
z-index 35 was trapped inside that context and never got to compete
with the composer's z-index 30 in the root stacking context, so the
operator still saw the badge clipped under the input box.

Same root cause on the flow page — `.flow-main .terminal-wrap`
inherits the same backdrop-filter rule.

Fix: anchor the pill in `.agent-main` / `.flow-main` instead of
`log.parentElement` (= `.terminal-wrap`). Both ancestors are
`position: absolute` with `overflow: hidden` but NO backdrop-filter
or other stacking-context creators, so the pill's z-index reaches
the root and properly floats above the composer.

Geometry unchanged — `.agent-main` / `.flow-main` and the
`.terminal-wrap` they contain both `inset: 0` the same area, so the
pill's `bottom: calc(--composer-h + 0.6em)` lands at the same y.

Also added `.flow-main .tail-pill { z-index: 35 }` (the flow page
was missing the per-page z-index bump that the agent page already
had).

`pillAnchor` is an existing opt in @hive/shared/terminal.js (the
default is `log.parentElement`); both consumers now set it explicitly.
2026-05-25 00:52:51 +02:00
iris
d4bb3e4aa0 agent: loose-ends flyout body on its own row (#376)
Operator: the per-entry header (from/sep/ts) + body were on the same
grid row, with the body squeezed into a 1fr column. In the side-
panel flyout's ~640px width, after the auto header columns ate
their share, the body wrapped over many narrow lines.

Fix: `.agent-inbox li` switches from `display: grid` to
`display: block`. The body element (currently a `<div>` for
loose-ends and a `<span>` for inbox messages) gets explicit
`display: block` so it always breaks to a new line under the
header. Light `padding-left + border-left` indent on the body
gives a visual relationship to the header row above without
needing a column structure.

`inbox-ts` + `inbox-sep` pick up small left margins to preserve
the inline spacing the grid's `gap: 0.5em` used to provide.

`.answer-form` drops its no-longer-applicable `grid-column: 1 / -1`
(replaced with a regular block layout + matching padding-left so
the form aligns with the question body it answers).

## Validation

`npm run build` clean. Bundle deltas: agent.css unchanged in
bundled size (rules swapped 1:1).

Browser smoke test isn't possible from inside iris's container.
Worth eyeballing post-deploy:
  - Loose-ends entries with long question/description bodies wrap
    to the panel width rather than getting clipped into a narrow
    fourth column.
  - The header line stays compact and readable.
  - Answer-form for a question loose-end still aligns visually
    under the question body it answers.

Closes #376.
2026-05-24 13:22:41 +02:00
iris
5615da9211 terminal: tail pill above floating chrome + autoscroll fix (#375)
Two bugs on the agent terminal page after the #362 overhaul:

## 1. `↓ N new` pill clipped by the composer

The fixed-overlay composer (z-index 30, agent.css) sits in the
root stacking context. The pill — `position: absolute` inside
`.live.terminal` with no z-index — defaults to its document-order
position in the body's stacking order, which the composer covers.

Fix: bump `.agent-main .tail-pill { z-index: 35 }` so the pill
participates in the root stacking context above the composer.
Scoped to the agent-page overlay layout — the shared `.tail-pill`
rule stays untouched (the dashboard's in-page layout doesn't need
the bump).

## 2. Autoscroll-on-new-message not firing when the operator was
   already at the bottom

`afterAppend()` in terminal.js was calling `isNearBottom()` AFTER
appending the new row. The new row's own height is already in
`scrollHeight` at that point, so for any row taller than the
NEAR_BOTTOM_PX threshold (48px — easily passed by a multi-line
message body, a tool-result summary, a markdown block), the check
returns false and the pill shows + scroll stays put.

Fix: capture `wasNearBottom = isNearBottom()` BEFORE the
`log.appendChild(...)` in each of `row` / `details` /
`detailsDiff`, pass it into `afterAppend(wasNearBottom)`. Now the
auto-scroll triggers whenever the operator was visually at the
bottom an instant before the row landed, regardless of the new
row's height.

Same shared `@hive/shared/terminal.js` is used by the dashboard
+ per-agent UI + the upcoming /flow.html page, so both pages
inherit the fix.

## Validation

`npm run build` clean.

Bundle deltas: shared terminal bundle re-inlined into both consumers
unchanged in size (the wasNearBottom variable is a single bool, no
measurable delta). Agent CSS +0.1kb (z-index property).

Browser smoke test isn't possible from inside iris's container —
worth eyeballing post-deploy:
  - With the operator scrolled to bottom, a tall message lands and
    the view scrolls to keep it visible (instead of pinning the
    pill).
  - The pill appears above the composer when the operator is
    scrolled up and new messages land.

Closes #375.
2026-05-24 13:22:18 +02:00
iris
e931c08739 frontend: vibec0re terminal overhaul (#360)
Per operator spec at #360#issuecomment-3333:
- full-screen terminal
- frosted-glass header overlaid on top
- inbox + loose-ends → flyout
- no a/b flag, just ship it

## Layout

`frontend/packages/agent/src/index.html` restructured to a three-zone
fixed-overlay shape:

- `<header.agent-header>` — fixed top, frosted glass via
  `backdrop-filter: blur(12px) saturate(140%)`. Holds icon + title +
  nav links + state-row (badges/buttons) + two new pill buttons that
  surface inbox / loose-ends counts (and open the side panel on click).
- `<main.agent-main>` — fills the viewport. Terminal positioned absolute
  inset:0 with padding-top/-bottom + scroll-padding equal to the
  floating header/composer heights so first/last rows stay reachable
  and `↓ N new` pill anchors land in the visible scroll zone.
- `<footer.agent-composer>` — fixed bottom, mirror-frosted. Owns
  `#term-input`; dropped the in-frame dashed separator (border-top
  + box-shadow on the bar already separate it from the terminal).
- `<div.side-panel>` — singleton drawer (copy of the dashboard
  pattern, candidate for extraction into @hive/shared). Inbox +
  loose-ends details render here instead of expanding inline.

Dropped from the page: the pre-banner ASCII shimmer (`<pre.banner>`)
and the in-page `<details>` collapsibles for inbox + loose-ends. The
banner JS path (`setBannerActive`) is now a no-op (early-returns on
missing element); kept as dead code rather than ripped out to keep
the diff focused.

## JS

`frontend/packages/agent/src/app.js`:

- New `Panel` singleton with `open(name, title, content)` +
  `close()` + `refresh(name, title, content)` (no-op if a different
  view owns the panel — lets live updates re-render an open view
  without grabbing focus from a closed one). Mirror of the
  dashboard's Panel module; the duplication is intentional for now.
- `renderInbox` + `renderLooseEnds` refactored: update the header
  pill counts, hide/show the pills, and `Panel.refresh` if the
  matching view is open. The list-building DOM logic moved into
  `buildInboxList` + `buildLooseEndsList` so the pill click handler
  can call them on the latest snapshot kept in `lastInbox` /
  `lastLooseEnds` module state.
- Pill click handlers `Panel.open(...)` with the freshly built list.
- Auto-expand behavior on first appearance dropped (the pill +
  count badge is the discoverable signal; auto-popping the flyout
  would interrupt whatever the operator is doing).
- `setHeader` no longer touches `#banner` (element removed); title +
  dashboard back-link + rebuild button still get appended to `#title`.

## CSS

`frontend/packages/agent/src/agent.css` major additions, scoped
`body.agent-shell` so the sibling `stats.html` (which doesn't apply
the shell class) keeps its normal-document scroll + `.banner` ASCII
header via a `body:not(.agent-shell)` block.

New CSS custom properties on :root: `--agent-header-h`,
`--agent-composer-h`, `--agent-frost-bg`, `--agent-frost-blur`. The
terminal's padding + scroll-padding derive from these so a single
height tweak ripples consistently.

Added `.header-pill` (inbox/loose-ends triggers) +
`.agent-status-overlay` (centred login card when status != online).
Side-panel rules copied from `dashboard.css` with one delta: width
caps at 640px (vs dashboard's 760px) since per-agent inbox / loose-
ends rows are narrower than approval diffs / file previews.

## Validation

- `npm run build` — succeeds both workspaces.
  - agent: `dist/static/{app.js (115kb), stats.js (435kb), agent.css (21kb)}`
  - dashboard unchanged (no shared sources touched).
- Browser smoke test isn't possible from inside iris's container
  (no JS engine) — op-side check on next deploy.

Closes #360.
2026-05-24 03:49:26 +02:00
iris
8bebd78895 frontend: add npm workspace scaffold under frontend/
Phase 1 of the backend/frontend code split (#273). Additive — no
existing code is touched; the legacy hive-c0re/assets, hive-ag3nt/
assets and hive-fr0nt/assets trees stay in place until the Rust
cutover later in this branch.

Layout:
  frontend/package.json                       npm workspaces root
  frontend/packages/shared/                   @hive/shared
    src/{base,terminal}.css + terminal.js     (ES module)
    src/index.js                              re-exports terminal.js
  frontend/packages/dashboard/                @hive/dashboard
    src/{index.html, app.js, dashboard.css}   ported from hive-c0re/assets
    build.mjs                                 esbuild config → dist/
  frontend/packages/agent/                    @hive/agent
    src/{index,stats,screen}.html + agent.css
        + {app,stats}.js                      ported from hive-ag3nt/assets
    build.mjs                                 esbuild config → dist/

Changes vs the existing assets:
- terminal.js is an ES module exporting { create, linkify } instead
  of assigning to window.HiveTerminal. The dashboard / agent app.js
  files re-expose them on window so the IIFE bodies keep working
  unchanged through Phase 1; the global aliases can be dropped in a
  follow-up once the IIFEs are unwrapped.
- marked is imported from the marked@4.3.0 npm package (replacing
  the vendored hive-fr0nt/assets/marked.umd.js bundle).
- chart.js is imported from chart.js@4.4.4 (replacing the jsDelivr
  CDN script tag on the per-agent stats page — page now works
  offline / on operator machines without internet egress).
- dashboard.css and agent.css both gain @import lines at the top
  that pull base.css + terminal.css from @hive/shared, replacing
  the runtime string concatenation in serve_css.
- index.html / stats.html collapse from three / two script tags to
  one type="module" tag pointing at the bundled output.

package-lock.json is intentionally omitted from this commit — npm
isn't available in the iris container yet (approval pending) and the
lockfile will land in the next commit on this branch once the
toolchain is in place. The PR will not be opened until it's there.

Phase 2 (nix derivations), Phase 3 (container plumbing + the
hyperhive.frontend.extraFiles option for per-agent layering), and
Phase 4 (Rust cutover to tower_http::ServeDir, delete hive-fr0nt
+ legacy assets dirs) land as follow-up commits on this same
branch.

Refs #273.
2026-05-23 14:51:01 +02:00