agent terminal: sticky-bottom auto-scroll with new-row pill
new rows no longer yank the view if the operator is scrolled up. threshold for 'near bottom' is 48px. when not near bottom, an amber '↓ N new' pill appears in the bottom-right of the terminal-wrap; clicking it jumps to bottom. scrolling back near bottom clears the counter. backfilled (history-replay) rows always scroll to bottom since the operator hasn't started reading yet.
This commit is contained in:
parent
875a8f5be4
commit
08f2ec5232
2 changed files with 78 additions and 2 deletions
|
|
@ -331,13 +331,58 @@
|
|||
row: (cls, text) => row(cls, text),
|
||||
clear: () => { log.innerHTML = ''; placeholder = null; },
|
||||
};
|
||||
|
||||
// Sticky-bottom auto-scroll. If the user is reading scrolled-up, new
|
||||
// rows do NOT yank the view. A floating "↓ N new" pill appears in
|
||||
// the bottom-right corner; clicking it jumps to bottom and clears
|
||||
// the counter. Scrolling back near the bottom also clears it.
|
||||
const NEAR_BOTTOM_PX = 48;
|
||||
let unseen = 0;
|
||||
let pill = null;
|
||||
function isNearBottom() {
|
||||
return log.scrollHeight - log.scrollTop - log.clientHeight <= NEAR_BOTTOM_PX;
|
||||
}
|
||||
function ensurePill() {
|
||||
if (pill) return pill;
|
||||
pill = document.createElement('button');
|
||||
pill.type = 'button';
|
||||
pill.className = 'tail-pill';
|
||||
pill.addEventListener('click', () => {
|
||||
log.scrollTop = log.scrollHeight;
|
||||
});
|
||||
log.parentElement.appendChild(pill);
|
||||
return pill;
|
||||
}
|
||||
function updatePill() {
|
||||
if (unseen <= 0) {
|
||||
if (pill) pill.classList.remove('visible');
|
||||
return;
|
||||
}
|
||||
ensurePill();
|
||||
pill.textContent = '↓ ' + unseen + ' new';
|
||||
pill.classList.add('visible');
|
||||
}
|
||||
log.addEventListener('scroll', () => {
|
||||
if (isNearBottom()) {
|
||||
unseen = 0;
|
||||
updatePill();
|
||||
}
|
||||
});
|
||||
function afterAppend() {
|
||||
if (currentNoAnim || isNearBottom()) {
|
||||
log.scrollTop = log.scrollHeight;
|
||||
} else {
|
||||
unseen += 1;
|
||||
updatePill();
|
||||
}
|
||||
}
|
||||
function row(cls, text) {
|
||||
clearPlaceholder();
|
||||
const e = document.createElement('div');
|
||||
e.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||
e.textContent = text;
|
||||
log.appendChild(e);
|
||||
log.scrollTop = log.scrollHeight;
|
||||
afterAppend();
|
||||
return e;
|
||||
}
|
||||
function details(cls, summary, body) {
|
||||
|
|
@ -352,7 +397,7 @@
|
|||
pre.textContent = body;
|
||||
d.appendChild(pre);
|
||||
log.appendChild(d);
|
||||
log.scrollTop = log.scrollHeight;
|
||||
afterAppend();
|
||||
return d;
|
||||
}
|
||||
function trim(s, n) { return s.length > n ? s.slice(0, n) + '…' : s; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue