fix(assistant): #91 maintien de l'ancre en haut pendant le streaming, libération au scroll manuel
CI / lint (push) Successful in 1m22s
CI / security (push) Successful in 1m0s
CI / test (push) Successful in 3m12s
CI / build (push) Successful in 50s
CI / e2e (push) Successful in 10m34s

This commit is contained in:
2026-09-15 16:24:57 -04:00
parent 731b3e4b47
commit 18b7dee2f6
3 changed files with 43 additions and 7 deletions
+19 -3
View File
@@ -122,6 +122,9 @@ class BooksLM {
this._menuSeq = 0;
// Timer that hides the "done" activity indicator after a short delay.
this._activityHideTimer = null;
// True while the latest question should stay pinned to the top of the
// thread (cleared as soon as the user scrolls manually).
this._pinnedTurn = false;
// Cache of every indexed path per vault, backing instant `@` filtering.
this._pathCache = null;
this._pathCacheVault = null;
@@ -887,6 +890,14 @@ class BooksLM {
this._activatePath(path, kind);
});
// A manual scroll releases the top-pinning of the latest question.
const messagesEl = panel.querySelector('.bookslm-messages');
if (messagesEl) {
const unpin = () => { this._pinnedTurn = false; };
messagesEl.addEventListener('wheel', unpin, { passive: true });
messagesEl.addEventListener('touchmove', unpin, { passive: true });
}
// Close the session / command menus when clicking elsewhere in the panel.
panel.addEventListener('click', (e) => {
if (!e.target.closest('.bookslm-history-menu') && !e.target.closest('.bookslm-btn-history')) {
@@ -1793,16 +1804,20 @@ class BooksLM {
}
if (opts.anchor) this._anchorLatestUser();
else if (this._isLoading && this._pinnedTurn) this._anchorLatestUser({ instant: true });
else container.scrollTop = prevTop;
}
/**
* Scroll the messages window so the most recent user post sits at the very
* top of the viewport (its `scroll-margin-top` keeps a breathing gap),
* reproducing Notion-AI's conversation anchoring.
* reproducing Notion-AI's conversation anchoring. The pin stays active
* while the answer streams, so the question never leaves the top — until
* the user scrolls manually (see the wheel/touch listeners in `_render()`).
*/
_anchorLatestUser() {
_anchorLatestUser(opts = {}) {
if (!this._panel) return;
this._pinnedTurn = true;
const container = this._panel.querySelector('.bookslm-messages');
if (!container) return;
const users = container.querySelectorAll('.bookslm-msg.user');
@@ -1810,9 +1825,10 @@ class BooksLM {
// Fallback (admin notes with no user post): anchor the newest message.
const el = users[users.length - 1] || all[all.length - 1] || container.firstElementChild;
if (!el) return;
const behavior = opts.instant ? 'auto' : 'smooth';
const apply = () => {
try {
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
el.scrollIntoView({ behavior, block: 'start' });
} catch {
const cRect = container.getBoundingClientRect();
const eRect = el.getBoundingClientRect();