From 47403595f1fecfd7d79fe4e4b95e5ca98c20c3f2 Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 25 May 2026 00:52:51 +0200 Subject: [PATCH] terminal: anchor tail pill outside .terminal-wrap stacking context (#375) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #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. --- frontend/packages/agent/src/app.js | 12 ++++++++++++ frontend/packages/dashboard/src/app.js | 10 ++++++++++ frontend/packages/dashboard/src/dashboard.css | 7 +++++++ 3 files changed, 29 insertions(+) diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 605078c..d72d1b8 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1143,6 +1143,18 @@ window.marked = marked; const term = HiveTerminal.create({ logEl: log, + // Anchor the `↓ N new` pill in `.agent-main` (NOT the default + // `log.parentElement` = `.terminal-wrap`). `.terminal-wrap` + // applies `backdrop-filter` for the frost effect, which + // creates a CSS stacking context — the pill's z-index 35 + // (agent.css #375) then gets TRAPPED inside that context and + // can't compete with the fixed composer's z-index 30 in the + // root stacking context. Anchoring in `.agent-main` (no + // backdrop-filter, no other stacking-context creators) lets + // the pill's z-index reach the root and properly float above + // the composer. Geometry unchanged — `.agent-main` and + // `.terminal-wrap` both `inset: 0` fill the same area. + pillAnchor: $('agent-main'), historyUrl: '/events/history', streamUrl: '/events/stream', renderers: { diff --git a/frontend/packages/dashboard/src/app.js b/frontend/packages/dashboard/src/app.js index 7560e4d..49394ce 100644 --- a/frontend/packages/dashboard/src/app.js +++ b/frontend/packages/dashboard/src/app.js @@ -2277,8 +2277,18 @@ window.marked = marked; // Register this row so future replies can reference it. if (ev.id != null && ev.id > 0) msgRowMap.set(ev.id, row); } + // Anchor the `↓ N new` pill in `.flow-main` (NOT the default + // `log.parentElement` = `.terminal-wrap`). `.terminal-wrap` + // applies `backdrop-filter`, which creates a CSS stacking + // context — the pill's z-index would otherwise be trapped + // inside and clipped under the fixed composer (issue #375). + // `.flow-main` has no backdrop-filter / stacking-context + // creators, so the pill's z-index reaches the root and floats + // above the composer. + const flowMain = document.querySelector('.flow-main'); HiveTerminal.create({ logEl: flow, + pillAnchor: flowMain, historyUrl: '/dashboard/history', streamUrl: '/dashboard/stream', renderers: { diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index 84a35fc..2747613 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -1421,8 +1421,15 @@ body.flow-shell .tabbar .tab.active.tab-link { scroll-padding-bottom: calc(var(--flow-composer-h) + 0.8em); overflow: auto; } +/* Tail pill (↓ N new): bottom offset clears the floating composer. + z-index escapes the stacking context the .terminal-wrap's + backdrop-filter creates (issue #375) — app.js anchors the pill + on .flow-main now (not .terminal-wrap), so this z-index reaches + the root stacking context and properly floats above the + composer at z-index 30. */ .flow-main .tail-pill { bottom: calc(var(--flow-composer-h) + 0.6em); + z-index: 35; } .flow-composer {