Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_ai_newest.js
T
DemonGatanjieuandClaude Opus 5.5 09136eabb5 Open AI tasks at their newest step and keep the grip off the scrollbar
A task now opens at its newest step instead of where the previous task
was scrolled to, and a timeline read at its foot follows new steps
whatever the task's state (before, only while a step was running).
Read further up, it stays put; a round button over the message box
leads back down, with the count of the AI's new steps on its corner.
It replaces the "new steps below" pill (ui_ai_newest.js).

The side column's grip lay over the timeline's scrollbar, so grabbing
the scrollbar often resized the column instead. It now has a gap of its
own beside the scrollbar, drawn as three dots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 02:13:31 +08:00

47 lines
2.0 KiB
JavaScript

/**
* The way back to a task's newest step, over the message box (ui_ai_workbench.js puts it there): a
* round button, there while the user reads more than a screen above the timeline's foot or the AI's
* steps came in since they left it, with how many on its corner; a click takes them down. atFoot
* tells the workbench whether a redraw keeps the timeline at its foot.
*/
import { translate as t } from './locales.js';
import { icon } from './ui_ai_icons.js';
const NEAR_BOTTOM = 60;
const below = scroller => scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
/** Whether `scroller` is scrolled to (about) its foot. */
export const atFoot = scroller => below(scroller) < NEAR_BOTTOM;
/** The button for the timeline in `scroller`; `arrived(count)` after a redraw that brought the AI's new steps. */
export function newestButton(scroller) {
const button = document.createElement('button');
button.type = 'button';
button.className = 'anomalous-ai-newest';
button.hidden = true;
const count = document.createElement('span');
count.className = 'anomalous-ai-newest-count';
button.append(icon('chevronDown', 18), count);
let unseen = 0;
const draw = () => {
if (atFoot(scroller)) unseen = 0;
button.hidden = !unseen && below(scroller) < scroller.clientHeight;
count.textContent = unseen > 99 ? '99+' : unseen ? String(unseen) : '';
button.title = unseen ? t('aiNewSteps', { count: unseen }) : t('aiToNewest');
button.setAttribute('aria-label', button.title);
};
button.onclick = () => scroller.scrollTo({ top: scroller.scrollHeight, behavior: 'smooth' });
const scrolled = () => {
if (!button.isConnected) scroller.removeEventListener('scroll', scrolled); // another task's view by now
else draw();
};
scroller.addEventListener('scroll', scrolled, { passive: true });
button.arrived = (steps) => {
if (!atFoot(scroller)) unseen += steps;
draw();
};
return button;
}