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)
This commit is contained in:
iris 2026-05-25 01:13:16 +02:00 committed by Mara
parent 88bc07fbbe
commit 69312b8553
3 changed files with 337 additions and 82 deletions

View file

@ -12,7 +12,10 @@
surface their counts as the only chrome they get. */
:root {
--agent-header-h: 4.6em;
/* Bumped to 6em (#394) so the agent icon can be a full-height
square identity anchor without crowding the two-row main column
(title + nav-links on top, state strip below). */
--agent-header-h: 6em;
--agent-composer-h: 3.6em;
--agent-frost-bg: rgba(30, 30, 46, 0.72);
--agent-frost-blur: blur(12px) saturate(140%);
@ -70,31 +73,41 @@ body.agent-shell {
z-index: 30;
min-height: var(--agent-header-h);
display: flex;
align-items: center;
gap: 1em;
padding: 0.55em 1em;
/* align-items: stretch lets the icon take the full header height
(it sizes itself via aspect-ratio off the stretched height). The
main column + pills column self-centre via inner layout. */
align-items: stretch;
gap: 0.9em;
padding: 0.5em 1em;
background: var(--agent-frost-bg);
-webkit-backdrop-filter: var(--agent-frost-blur);
backdrop-filter: var(--agent-frost-blur);
border-bottom: 1px solid var(--purple-dim);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
flex-wrap: wrap;
}
.agent-header-title {
/* Main column: title row on top, state strip below (#394). Centred
vertically against the full-height icon on the left. */
.agent-header-main {
display: flex;
flex-direction: column;
gap: 0.1em;
justify-content: center;
gap: 0.45em;
min-width: 0;
flex: 1 1 auto;
}
.agent-header-title h2 {
.agent-header-row {
display: flex;
align-items: center;
gap: 0.8em;
flex-wrap: wrap;
min-width: 0;
}
.agent-header-title-row h2 {
margin: 0;
line-height: 1;
}
.agent-nav {
/* Slim nav row directly under the title keeps the operator's
fingers near stats/screen/forge without dominating the header. */
display: flex;
flex-wrap: wrap;
gap: 0.4em 0.8em;
@ -103,10 +116,21 @@ body.agent-shell {
.agent-state-row {
margin: 0;
gap: 0.5em;
}
/* Right cluster flyout pills stacked / inline with the overflow
trigger. Vertically centred against the full-height icon, no
wrap; pills can drop to a row of their own under crowding via
the flex-wrap of `.agent-header-pills` itself. */
.agent-header-pills {
display: flex;
align-items: center;
gap: 0.5em;
flex-shrink: 0;
flex-wrap: wrap;
justify-content: flex-end;
align-self: center;
}
h2, h3 {
@ -116,11 +140,126 @@ h2, h3 {
text-shadow: 0 0 8px rgba(203, 166, 247, 0.4);
}
.agent-icon {
width: 44px;
height: 44px;
border-radius: 6px;
/* Full-height square identity anchor (#394 mara's spec). The
`align-items: stretch` on .agent-header stretches the icon's
`<img>` box; `aspect-ratio: 1` keeps it square; `height: 100%`
makes it follow the header's actual height through resizes /
wrap. width:auto + aspect-ratio derives the width from height. */
height: 100%;
width: auto;
aspect-ratio: 1;
flex-shrink: 0;
box-shadow: 0 0 14px -2px rgba(203, 166, 247, 0.35);
border-radius: 8px;
box-shadow: 0 0 18px -2px rgba(203, 166, 247, 0.4);
object-fit: cover;
}
/* Meta-nav links (stats / screen / forge / dashboard / extras)
no underline (#394 mara's spec); hover lights with cyan glow +
subtle background tint. Reads as a row of soft tabs rather than
default-styled inline anchors. */
.agent-nav-link {
color: var(--cyan);
text-decoration: none;
font-size: 0.85em;
letter-spacing: 0.04em;
padding: 0.1em 0.35em;
border-radius: 3px;
text-shadow: 0 0 4px rgba(137, 220, 235, 0.4);
transition: color 0.15s ease, text-shadow 0.15s ease, background 0.15s ease;
}
.agent-nav-link:hover {
color: var(--fg);
background: rgba(137, 220, 235, 0.08);
text-shadow: 0 0 10px rgba(137, 220, 235, 0.85);
}
/* Overflow menu trigger `` round button on the right of the
pills row. Quiet by default, lights on hover / open (#394). */
.overflow-btn {
background: transparent;
border: 1px solid var(--purple-dim);
color: var(--muted);
border-radius: 999px;
width: 2em;
height: 1.8em;
font-size: 1em;
line-height: 1;
cursor: pointer;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
transition: color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.overflow-btn:hover,
.overflow-btn[aria-expanded="true"] {
color: var(--purple);
border-color: var(--purple);
box-shadow: 0 0 10px -2px var(--purple);
}
/* Overflow popover rebuild + new-session (and the dashboard
back-link, prepended in app.js setHeader). Positioned in JS so
the menu's top-right corner anchors under the trigger button. */
.overflow-menu {
position: fixed;
background: var(--agent-frost-bg);
-webkit-backdrop-filter: var(--agent-frost-blur);
backdrop-filter: var(--agent-frost-blur);
border: 1px solid var(--purple-dim);
border-radius: 6px;
padding: 0.35em;
display: flex;
flex-direction: column;
gap: 0.15em;
z-index: 40;
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
min-width: 14em;
}
.overflow-item {
background: transparent;
border: 1px solid transparent;
color: var(--fg);
font-family: inherit;
font-size: 0.9em;
text-align: left;
padding: 0.4em 0.7em;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.6em;
letter-spacing: 0.06em;
text-decoration: none;
text-shadow: 0 0 4px currentColor;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.overflow-item:hover {
background: rgba(203, 166, 247, 0.08);
border-color: var(--purple-dim);
}
.overflow-item-icon {
font-size: 1.05em;
width: 1.4em;
text-align: center;
flex-shrink: 0;
}
.overflow-item-rebuild { color: var(--amber); }
.overflow-item-new-session { color: var(--amber); }
.overflow-item-rebuild:hover,
.overflow-item-new-session:hover {
background: rgba(250, 179, 135, 0.1);
border-color: var(--amber);
}
.overflow-item-dashboard { color: var(--cyan); }
.overflow-item-dashboard:hover {
background: rgba(137, 220, 235, 0.1);
border-color: var(--cyan);
}
.overflow-item:disabled {
opacity: 0.4;
cursor: progress;
}
/* Header pill — inbox / loose-ends triggers. Compact, count-prominent. */
@ -292,12 +431,9 @@ pre.diff {
word-break: break-all;
max-height: 30em;
}
#state-row {
margin: 0.4em 0 0.2em;
display: flex;
align-items: center;
gap: 0.6em;
}
/* `#state-row` layout is provided by `.agent-state-row` /
`.agent-header-row` above (#394) kept as a no-op selector
anchor in case any future rule needs to scope by id. */
/* Per-agent inbox section collapsible, dim, lives between the
state row and the terminal so the operator can peek at what
landed without scrolling through the live tail. */