diff --git a/frontend/packages/agent/src/agent.css b/frontend/packages/agent/src/agent.css index 6222bcb..64ed0fc 100644 --- a/frontend/packages/agent/src/agent.css +++ b/frontend/packages/agent/src/agent.css @@ -548,9 +548,12 @@ pre.diff { overflow: auto; } /* Tail pill (↓ N new): nudged up so it floats clear of the composer - rather than colliding with the frosted bar. */ + rather than colliding with the frosted bar. z-index bumped above + the composer (z-30) so the pill sits on the top layer instead of + being clipped by the floating chrome (issue #375). */ .agent-main .tail-pill { bottom: calc(var(--agent-composer-h) + 0.6em); + z-index: 35; } /* Composer chrome — used to live inside `.terminal-wrap`; now lives diff --git a/frontend/packages/shared/src/terminal.js b/frontend/packages/shared/src/terminal.js index 0726a8c..1c350ff 100644 --- a/frontend/packages/shared/src/terminal.js +++ b/frontend/packages/shared/src/terminal.js @@ -88,8 +88,16 @@ export function create(opts) { log.addEventListener('scroll', () => { if (isNearBottom()) { unseen = 0; updatePill(); } }); - function afterAppend() { - if (currentNoAnim || isNearBottom()) { + // Auto-scroll decision uses the PRE-append scroll position + // (issue #375). Checking after the append underestimates + // "nearness" because the new row's own height has already pushed + // `scrollHeight - scrollTop - clientHeight` past the threshold, + // even when the user was visually at the bottom an instant ago. + // Each row/details/detailsDiff captures `nearBottomBeforeAppend` + // and hands it to afterAppend so the auto-scroll triggers + // whenever the operator was at the bottom when the row landed. + function afterAppend(wasNearBottom) { + if (currentNoAnim || wasNearBottom) { log.scrollTop = log.scrollHeight; } else { unseen += 1; @@ -112,15 +120,17 @@ export function create(opts) { } function row(cls, text) { clearPlaceholder(); + const wasNearBottom = isNearBottom(); const e = document.createElement('div'); e.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); e.appendChild(linkify(text)); log.appendChild(e); - afterAppend(); + afterAppend(wasNearBottom); return e; } function details(cls, summary, body) { clearPlaceholder(); + const wasNearBottom = isNearBottom(); const d = document.createElement('details'); d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); const s = document.createElement('summary'); @@ -131,11 +141,12 @@ export function create(opts) { pre.appendChild(linkify(body)); d.appendChild(pre); log.appendChild(d); - afterAppend(); + afterAppend(wasNearBottom); return d; } function detailsDiff(cls, summary, body) { clearPlaceholder(); + const wasNearBottom = isNearBottom(); const d = document.createElement('details'); d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); const s = document.createElement('summary'); @@ -153,7 +164,7 @@ export function create(opts) { } d.appendChild(pre); log.appendChild(d); - afterAppend(); + afterAppend(wasNearBottom); return d; }