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.