Commit graph hyperhive/frontend/packages/dashboard/src/dashboard.css
Author SHA1 Message Date
iris
002419ba4d dashboard: drop stale data-spw note in side-panel-drawer comment (#451)
argus nit on #456: the CSS comment claimed "The body adds
data-spw=<px> once persisted" — that's leftover from a prior
design and no such attribute is ever set. The actual flow is
localStorage → JS reads on Panel.bind → sets --side-panel-w via
inline style. Replaced the misleading line with an accurate
description of the persistence path.
2026-05-26 01:27:42 +02:00
iris
f4635cc256 dashboard: full-height inbox + drag-to-resize side panel (#450 #451)
two related flyout improvements — both small, single PR.

#450 — full-height inbox

dropped the .inbox max-height: 24em cap. the side-panel-body
already provides overflow: auto, so the cap was just clamping the
inbox list short of the available panel height on tall viewports.
the inbox now fills as much of the panel as it needs and the
panel itself scrolls.

#451 — drag-to-resize side panel

added a 6px hit-strip on the drawer's left edge. mousedown/move/
up handlers track the drag and update --side-panel-w on the
drawer; the CSS variable defaults to min(760px, 94vw) (preserving
pre-#451 behaviour) and is clamped to [320px, 96vw] so a bad
stored value can never wedge the drawer off-screen.

ergonomics

- handle is invisible at rest, mauve glow on hover + during drag
  so the affordance is discoverable but doesn't compete with the
  2px mauve border-left for the visual boundary.
- body.side-panel-resizing class forces ew-resize cursor + kills
  user-select page-wide so the cursor doesn't flip back to
  default the moment it leaves the 6px band during a fast drag.
- final width persists to localStorage (key
  hyperhive:side-panel-width) so it survives reload. window
  resize re-clamps so a stored width that exceeds the new 96vw
  shrinks accordingly.
- handle is a separator role with aria-orientation: vertical +
  aria-label for screen readers.

files

- frontend/packages/dashboard/src/dashboard.css
  - .inbox: dropped max-height (#450).
  - .side-panel-drawer: width = var(--side-panel-w, min(760px,
    94vw)) + min/max clamp (#451).
  - new .side-panel-resize + body.side-panel-resizing rules.
- frontend/packages/dashboard/src/common.js
  - Panel.bind now also calls applyStoredWidth + bindResize.
  - resize handle is prepended to the drawer at bind time so
    every flyout (inbox, file preview, diff, journal) gets it.

validation

- npm run build --workspace=@hive/dashboard clean. CSS 40.9kb →
  41.3kb. app.js + flow.js grew ~0.5kb each (resize handler).
- browser smoke test isn't possible from inside iris's container;
  the resize math is straightforward (width = innerWidth -
  clientX, clamped) and the CSS variable + localStorage
  persistence are standard patterns.
2026-05-26 01:27:42 +02:00
iris
a366c00e0e dashboard: agent selection + bulk action bar (#443)
per mara on #443: "dont show all the restart buttons etc., just
show state and links. instead, clicking an agent icon selects that
agent. you can select as many as you like. then you can run an
action on all of them."

selection model

- module-level selectionState = Set<string> of agent logical
  names. clicking a container-row icon toggles membership; Esc
  clears the whole set (ignored when an editable element has
  focus so typing in compose / answer / journal-search isn't
  intercepted).
- icon now has role="button" + tabindex="0" so it's
  keyboard-accessible; aria-pressed reflects the toggle state.
  hover + focus-visible get a mauve ring.
- selected rows get a .selected class — mauve outline +
  faint mauve wash on the row, brighter ring on the icon.
- on every renderContainers pass, stale selections (agents
  destroyed while selected) are pruned defensively.

sticky action bar

- new #selection-bar in index.html — fixed bottom strip with
  count chip, name list, action buttons, clear button. hidden
  when selection empty. styled to match the flow composer's
  frosted-mauve chrome (vibecore family).
- per mara's option B answer: every action button is visible;
  buttons that don't apply to the whole selection are disabled
  with a tooltip explaining WHY ("iris is already stopped" etc).
  .btn:disabled styling added.
- actions: R3ST4RT / ST0P / ST4RT / R3BU1LD / DESTR0Y / PURG3.
  per-action confirm prompt lists the names being acted on.
  when the selection includes the manager the ST0P prompt calls
  out the consequence (approvals + meta operations pause until
  manager is restarted). DESTR0Y/PURG3 stay sub-agent-only;
  including the manager disables them with a clear tooltip.
- actions POST per agent in a loop to the existing endpoints
  (/restart/{name}, /kill/{name}, etc.); no new backend wire
  surface. event-driven derived stores (containersState,
  rebuildQueueState) already update live via SSE — no manual
  refetch.
- failures from any individual POST surface in a single alert
  at the end rather than spamming dialogs mid-loop.

per-card actions removed

- the in-card R3ST4RT / ST0P / ST4RT / R3BU1LD / DESTR0Y / PURG3
  block is gone. cards now show identity / state / nav-strip /
  status text / drill-ins only — "state and links" per mara.
  the contextual needs-update chip in the head row stays.

manager stop

- the "also make manager stoppable" half of #443 ships as PR
  #445 (separate small backend change). this PR depends on #445
  for the bulk-stop button to actually work on selections
  containing the manager; until #445 merges, ST0P on a
  manager-included selection will fail individually with a 500
  for the manager (other selected agents are still stopped;
  failures bucket into the end-of-loop alert).

build clean: npm run build --workspace=@hive/dashboard. no
backend changes here.
2026-05-26 00:41:02 +02:00
damocles
b42b219f9b dashboard: green chip for approval source (iris nit on #436) 2026-05-26 00:29:42 +02:00
iris
c015f62764 dashboard: drop max-width cap, full-width layout (#416)
Mara: remove the 90em max-width so the dashboard fills wide
screens instead of boxing content into a centred column with
empty margins. `padding: 0 1.5em 1.5em` stays as the small
edge-of-viewport gutter; `.dashboard-chrome { margin: 0 -1.5em
... }` continues to pull the sticky chrome bar edge-to-edge
through that gutter.
2026-05-25 20:59:25 +02:00
iris
c08218acdc dashboard: slug to page footer, not chrome (#389 follow-up)
PR #392 moved the slug below the tab strip but kept it INSIDE the
sticky chrome — Mara's report ("still at old position everywhere
except flow tab") makes the original intent clearer: the slug
should be at the page BOTTOM, with the chrome reduced to pure
navigation.

## index.html
- Remove the banner-thin from `.dashboard-chrome` entirely.
- Move it into the existing `<footer>` element (above the divider
  + project-link line). Sits at the bottom of every tab pane after
  the main content scrolls past.

## flow.html
- Remove the banner-thin from the chrome. The flow page is a
  full-viewport terminal with `body { overflow: hidden }` and
  no normal-flow footer position — the slug simply doesn't appear
  here. The frosted chrome is purely the tab strip now.

## dashboard.css
- `--flow-header-h: 4.7em → 3.6em` — chrome is shorter without
  the banner; terminal padding-top + tail-pill offset + inbox-pill
  top all derive from this variable, so they follow automatically.
- `footer .banner-thin { margin-bottom: 0.8em }` so the slug
  doesn't crash into the divider + link line below it.

No JS changes. Build clean.
2026-05-25 01:59:12 +02:00
iris
7743c07380 dashboard: connect tree-prefix vertical bars across taller rows (#388)
The container-row tree-prefix used text box-drawing glyphs (├ └ │)
positioned with `top: 0.6em` — a single text-line tall. Once rows
grew past one line (5em square icon + multi-line body), the `│`
columns of consecutive siblings no longer touched, leaving visible
breaks in the tree.

Replace the text-glyph string with structured DOM: one `.tree-lane`
per depth column. Continuation lanes (`.lane-line`) paint a 1px
border-left spanning the full row height + the `.containers` gap
below, so adjacent siblings' bars visually merge into one unbroken
vertical. The row's own joint lane is `├` (branch — bar continues
below) or `└` (last — bar stops at icon midline), with a horizontal
stub at 3.1em (row padding-top + icon half-height) reaching to the
icon edge.

Joint y / stub width are derived from the 5em icon + 0.6em row
padding-top + 0.8em row padding-left so they meet the icon cleanly.
2026-05-25 01:09:03 +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
ba669d2d6c flow: keep the dashboard tab strip on the flow page (#383)
Operator wanted the tab header visible on /flow.html so switching
tabs doesn't require navigating back to / first.

The flow page now reuses the same `<header class="dashboard-chrome">`
markup the dashboard renders, with a few tweaks:

- The SW4RM / Y3R C4LL / SYST3M tabs are cross-page links
  (`href="/#swarm"` etc.) — clicking lands on the dashboard with the
  destination tab pre-active via the hash router.
- The FL0W tab is rendered `.active.tab-link` + `aria-current="page"`
  so it reads as the current view (no clickable arrow / "go here"
  affordance — you're already here).
- Banner-thin echoes the dashboard for visual continuity.
- Notif controls cohabit with the tabs (same IDs the dashboard uses,
  so app.js's NOTIF binding picks them up unchanged).

Layout glue:
- `body.flow-shell .dashboard-chrome.flow-chrome` overrides the
  dashboard's `position: sticky` with `position: fixed` so the
  chrome stays put under flow-shell's `overflow: hidden` body
  layout, keeping the terminal full-viewport behind/beneath.
- New rule for the active FL0W tab — the `.tab-link` styling on the
  dashboard otherwise reads as a passive cross-page link; here we
  need it lit-up like a regular active tab.
- `--flow-header-h` bumped from 4.2em → 4.7em to match the natural
  height of the tab strip + banner combo. Terminal padding +
  inbox-pill top offset both derive from this variable, so they
  follow automatically.

Removed:
- Legacy `.flow-title`, `.flow-hint`, `.flow-back` CSS rules (their
  HTML counterparts are gone — the tab strip carries the
  identity now).
- The `<a class="flow-back">← d4shb04rd</a>` link and the
  `<h2 class="flow-title">` from flow.html.

## Validation

`npm run build` clean.
  dashboard.css: 38kb → 37kb (legacy rules removed, new shared-
                  chrome rules are smaller)
  flow.html:    4.4kb → 4.7kb (tab strip replaces title bar)
  app.js:       unchanged (no JS changes — the tab navigation is
                pure HTML href + cross-page hash)

Closes #383.
2026-05-25 00:12:28 +02:00
iris
9666cb8c3f dashboard: tab-bar restructure + extract FL0W to /flow.html (#369)
Operator: 'option A (tabs)' (#369#issuecomment-3434) +
'yes terminal can be a separate page' (#369#issuecomment-3437).

## Tab framework

`index.html` becomes a 3-tab dashboard with a sticky chrome header:

- `◆ SW4RM ◆`    — containers list (the central thing)
- `◆ Y3R C4LL ◆` — pending approvals + operator-targeted questions
- `◆ SYST3M ◆`   — meta inputs + rebuild queue + reminders + tombstones

Hash routing: `#swarm` / `#call` / `#system` (empty → SW4RM).
F5-reloadable + back-button-aware without a router framework.

SSE stays alive across tab switches — count pills on inactive tabs
update live so the operator never loses pulse on what's happening
elsewhere:

  - SW4RM:  containers with needs_update
  - Y3R C4LL: approvals.pending + questions.pending (attn-coloured pill)
  - SYST3M: rebuild_queue entries in Queued|Running

Pills hidden when count is zero. setInterval(1s) polls the existing
state stores (cheap, no per-renderer hookup needed).

## FL0W as its own page

The all-agents chat moves to /flow.html — full-viewport vibec0re
layout mirroring the per-agent live page (#362):

- Fixed-overlay frosted-glass header at top (back link + title +
  notif controls), backdrop-filter blur shows the scrolled chat
  text behind.
- Full-viewport terminal, scroll-padded for the floating chrome so
  first/last rows stay reachable.
- Fixed-overlay frosted composer at the bottom.
- Operator inbox surfaces via a pill (📬 inbox · N) in the upper
  right — click opens the side-panel flyout with the message list.

In the dashboard tab strip, FL0W is the right-most entry but
renders as a `<a class="tab tab-link" href="/flow.html">` — clicking
navigates to the page rather than swapping a pane. Same pattern
back from flow.html via the `← d4shb04rd` link.

## Implementation notes

- New `/flow.html` page rendered by the same bundled `app.js` — the
  flow page just doesn't have the dashboard-chrome DOM, so the
  matching renderers no-op silently (each `if (!el) return`).
  Avoids splitting the bundle for v1; can extract later if size
  becomes a concern.
- `Panel` module gains `openNamed(name, …)` + `refresh(name, …)` —
  the legacy untyped `open(title, content)` calls clear the owner,
  so file-preview / diff / log drill-ins behave unchanged. `refresh`
  is no-op when a different view owns the panel, so live message
  events re-render the inbox flyout only when it's actually open.
- `renderInbox` updates BOTH the dashboard's inline `#inbox-section`
  (now living on the flow page) AND the flow page's pill count +
  side-panel refresh. The dashboard's empty FL0W tab is removed —
  inbox + message flow + compose box only exist in flow.html.
- Banner shrinks to a thin Catppuccin gradient strip at the top of
  the dashboard chrome (dropped the multi-line ASCII art —
  affectionate but pure chrome budget in a tabbed layout).
- `build.mjs` copies both `index.html` + `flow.html` into dist.

## Validation

`npm run build` clean. Dashboard bundle deltas:
  app.js  150kb → 152kb  (tab routing + count pills + named-Panel)
  dashboard.css 33kb → 38kb (tab chrome + flow page layout)
  + dist/flow.html  4.4kb

Browser smoke test isn't possible from inside iris's container
(no JS engine) — drafting as a PR for operator visual review on
next deploy. Worth eyeballing:
  - Tab switching feels right; counts update live across SSE events
  - FL0W page reads like the agent live page (frosted header + composer)
  - Inbox pill opens flyout; live message arrivals refresh it
  - Back link from flow → dashboard returns to last tab via the
    URL hash (browser remembers the hash across page nav)

Closes #369.
2026-05-24 13:03:53 +02:00
iris
8c7bc850f3 dashboard: render agent topology as a tree in the container list
Closes #363 (frontend half of milestone #361). Consumes the
`ContainerView.parent: Option<String>` field landing in damocles'
backend slice — when present, the container list renders depth-first
with sibling-position tree glyphs (├─ / └─ joints + │ continuation
columns). When absent (pre-#361 state — every container's `parent` is
None) the tree collapses to a flat list with no glyphs and no indent
— bit-identical to the legacy render.

## Tree shape

- Roots: containers whose `parent` is None OR whose parent name isn't
  in the current container set (orphan tolerance).
- Sibling ordering: alphabetical by name within each level (matches
  damocles' wire spec at #363#issuecomment-3356).
- Cycle safety: any container not reached via the root-walk gets
  emitted as a root at the end — no agent ever silently disappears
  from the list when the topology is malformed.
- Tree glyphs: ancestor at depth d contributes a │ continuation column
  when that ancestor has more siblings below; otherwise a 3-space gap.
  The joint is ├─ for non-last siblings, └─ for the last child.
- Depth-0 ancestor column is suppressed: roots already separate
  visually as top-level rows, no need for a column 0 vertical line.

## DOM / CSS

- New `buildAgentTree(containers)` + `treePrefix(node)` helpers in
  app.js. The render loop walks the tree-ordered list instead of the
  legacy alphabetical containers array.
- Each container row gets `data-depth=N` (only when N > 0) and a
  `<span class="tree-prefix">…</span>` prepended (absolute-positioned
  into the row's left margin so the existing flex icon/body layout
  isn't disrupted).
- CSS: per-depth `margin-left` step rules for depths 1-6 (hardcoded
  rather than typed-attr() because CSS Values 5 is Chromium-only as
  of 2026). 6 depths cover any reasonable hive topology with
  headroom; deeper agents render at depth 6 indent without further
  step — visually clamps gracefully.
- `.tree-prefix` rendered with `var(--purple-dim)` so the structural
  lines read as supporting chrome, not as content.

## Validation

- `npm run build` clean. Bundle deltas: dashboard app.js
  +1.8kb (tree builder + treePrefix + render-loop tweak),
  dashboard.css +0.4kb (tree-prefix + per-depth indent rules).
- The render is a no-op until `ContainerView.parent` is populated —
  validation in production deferred to once damocles' meta-topology
  field lands. The pre-#361 path (every parent=None) is exercised
  by every existing dashboard load.
- Forward-compatible with damocles' design pivot at #364 (topology
  source moved from agent.nix to meta/topology.json). The wire shape
  on ContainerView is unchanged from the frontend's perspective — the
  field is just sourced from a different backend store.
2026-05-24 12:04:19 +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