terminal: tail pill above floating chrome + autoscroll fix (#375)
Two bugs on the agent terminal page after the #362 overhaul: ## 1. `↓ N new` pill clipped by the composer The fixed-overlay composer (z-index 30, agent.css) sits in the root stacking context. The pill — `position: absolute` inside `.live.terminal` with no z-index — defaults to its document-order position in the body's stacking order, which the composer covers. Fix: bump `.agent-main .tail-pill { z-index: 35 }` so the pill participates in the root stacking context above the composer. Scoped to the agent-page overlay layout — the shared `.tail-pill` rule stays untouched (the dashboard's in-page layout doesn't need the bump). ## 2. Autoscroll-on-new-message not firing when the operator was already at the bottom `afterAppend()` in terminal.js was calling `isNearBottom()` AFTER appending the new row. The new row's own height is already in `scrollHeight` at that point, so for any row taller than the NEAR_BOTTOM_PX threshold (48px — easily passed by a multi-line message body, a tool-result summary, a markdown block), the check returns false and the pill shows + scroll stays put. Fix: capture `wasNearBottom = isNearBottom()` BEFORE the `log.appendChild(...)` in each of `row` / `details` / `detailsDiff`, pass it into `afterAppend(wasNearBottom)`. Now the auto-scroll triggers whenever the operator was visually at the bottom an instant before the row landed, regardless of the new row's height. Same shared `@hive/shared/terminal.js` is used by the dashboard + per-agent UI + the upcoming /flow.html page, so both pages inherit the fix. ## Validation `npm run build` clean. Bundle deltas: shared terminal bundle re-inlined into both consumers unchanged in size (the wasNearBottom variable is a single bool, no measurable delta). Agent CSS +0.1kb (z-index property). Browser smoke test isn't possible from inside iris's container — worth eyeballing post-deploy: - With the operator scrolled to bottom, a tall message lands and the view scrolls to keep it visible (instead of pinning the pill). - The pill appears above the composer when the operator is scrolled up and new messages land. Closes #375.
This commit is contained in:
parent
14b79f43cf
commit
5615da9211
2 changed files with 20 additions and 6 deletions
|
|
@ -548,9 +548,12 @@ pre.diff {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
/* Tail pill (↓ N new): nudged up so it floats clear of the composer
|
/* 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 {
|
.agent-main .tail-pill {
|
||||||
bottom: calc(var(--agent-composer-h) + 0.6em);
|
bottom: calc(var(--agent-composer-h) + 0.6em);
|
||||||
|
z-index: 35;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Composer chrome — used to live inside `.terminal-wrap`; now lives
|
/* Composer chrome — used to live inside `.terminal-wrap`; now lives
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,16 @@ export function create(opts) {
|
||||||
log.addEventListener('scroll', () => {
|
log.addEventListener('scroll', () => {
|
||||||
if (isNearBottom()) { unseen = 0; updatePill(); }
|
if (isNearBottom()) { unseen = 0; updatePill(); }
|
||||||
});
|
});
|
||||||
function afterAppend() {
|
// Auto-scroll decision uses the PRE-append scroll position
|
||||||
if (currentNoAnim || isNearBottom()) {
|
// (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;
|
log.scrollTop = log.scrollHeight;
|
||||||
} else {
|
} else {
|
||||||
unseen += 1;
|
unseen += 1;
|
||||||
|
|
@ -112,15 +120,17 @@ export function create(opts) {
|
||||||
}
|
}
|
||||||
function row(cls, text) {
|
function row(cls, text) {
|
||||||
clearPlaceholder();
|
clearPlaceholder();
|
||||||
|
const wasNearBottom = isNearBottom();
|
||||||
const e = document.createElement('div');
|
const e = document.createElement('div');
|
||||||
e.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
e.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||||
e.appendChild(linkify(text));
|
e.appendChild(linkify(text));
|
||||||
log.appendChild(e);
|
log.appendChild(e);
|
||||||
afterAppend();
|
afterAppend(wasNearBottom);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
function details(cls, summary, body) {
|
function details(cls, summary, body) {
|
||||||
clearPlaceholder();
|
clearPlaceholder();
|
||||||
|
const wasNearBottom = isNearBottom();
|
||||||
const d = document.createElement('details');
|
const d = document.createElement('details');
|
||||||
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||||
const s = document.createElement('summary');
|
const s = document.createElement('summary');
|
||||||
|
|
@ -131,11 +141,12 @@ export function create(opts) {
|
||||||
pre.appendChild(linkify(body));
|
pre.appendChild(linkify(body));
|
||||||
d.appendChild(pre);
|
d.appendChild(pre);
|
||||||
log.appendChild(d);
|
log.appendChild(d);
|
||||||
afterAppend();
|
afterAppend(wasNearBottom);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
function detailsDiff(cls, summary, body) {
|
function detailsDiff(cls, summary, body) {
|
||||||
clearPlaceholder();
|
clearPlaceholder();
|
||||||
|
const wasNearBottom = isNearBottom();
|
||||||
const d = document.createElement('details');
|
const d = document.createElement('details');
|
||||||
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||||
const s = document.createElement('summary');
|
const s = document.createElement('summary');
|
||||||
|
|
@ -153,7 +164,7 @@ export function create(opts) {
|
||||||
}
|
}
|
||||||
d.appendChild(pre);
|
d.appendChild(pre);
|
||||||
log.appendChild(d);
|
log.appendChild(d);
|
||||||
afterAppend();
|
afterAppend(wasNearBottom);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue