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
|
|
@ -130,6 +130,7 @@ pre.diff {
|
|||
so anything that bleeds through (page banner glow, scroll position)
|
||||
reads as out-of-focus depth instead of sharp competing detail. */
|
||||
.terminal-wrap {
|
||||
position: relative;
|
||||
background: rgba(17, 17, 27, 0.78);
|
||||
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
||||
backdrop-filter: blur(8px) saturate(120%);
|
||||
|
|
@ -227,6 +228,36 @@ pre.diff {
|
|||
from { opacity: 0; transform: translateY(4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
/* "↓ N new" pill: shown when new rows arrive while the operator is
|
||||
scrolled up; click to jump to bottom. */
|
||||
.tail-pill {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
bottom: 4.2em;
|
||||
background: var(--amber);
|
||||
color: #11111b;
|
||||
font-family: inherit;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.08em;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 0.35em 0.9em;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 14px -2px rgba(250, 179, 135, 0.85);
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
pointer-events: none;
|
||||
transition: opacity 160ms ease, transform 160ms ease;
|
||||
}
|
||||
.tail-pill.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
}
|
||||
.tail-pill:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
details.row {
|
||||
white-space: normal;
|
||||
padding-left: 0.5em;
|
||||
|
|
|
|||
|
|
@ -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