fix(#4702): co-seed _lastMessageClientHeight with every programmatic _lastScrollTop write (Codex gate)
Codex found the guard had a first-event gap: programmatic seeds of _lastScrollTop (_setMessageScrollToBottom, scrollToBottom, snapshot restore paths) didn't seed _lastMessageClientHeight, so the FIRST native toolbar-collapse scroll saw a null prior height -> grew=false -> the false-unpin still fired. Now every programmatic _lastScrollTop=el.scrollTop is paired with _lastMessageClientHeight=el.clientHeight.
This commit is contained in:
1 parent
fe40912eb9
commit
671f4504ae
2 files changed
+19
-5
No files matched your search
+5
-5
@@ -4472,7 +4472,7 @@ function _setMessageScrollToBottom(){
|
||||
if(!el) return;
|
||||
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
||||
el.scrollTop=el.scrollHeight;
|
||||
_lastScrollTop=el.scrollTop;
|
||||
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||
_nearBottomCount=2;
|
||||
_scrollPinned=true;
|
||||
requestAnimationFrame(()=>{
|
||||
@@ -4487,7 +4487,7 @@ function _setMessageScrollToBottom(){
|
||||
return;
|
||||
}
|
||||
el.scrollTop=el.scrollHeight;
|
||||
_lastScrollTop=el.scrollTop;
|
||||
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||
_nearBottomCount=2;
|
||||
_scrollPinned=true;
|
||||
_deferClearProgrammaticScroll();
|
||||
@@ -4612,7 +4612,7 @@ function _settleFinalScroll(token){
|
||||
}
|
||||
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
||||
el.scrollTop=el.scrollHeight;
|
||||
_lastScrollTop=el.scrollTop;
|
||||
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||
_nearBottomCount=2;
|
||||
_scrollPinned=true;
|
||||
_deferClearProgrammaticScroll();
|
||||
@@ -11002,7 +11002,7 @@ function _restoreMessageScrollSnapshot(snapshot){
|
||||
el.scrollTop=Math.max(0,Math.min(Number(snapshot.top)||0,maxTop));
|
||||
}
|
||||
// Sync _lastScrollTop after programmatic restore so sticky-unpin does not false-trigger (#1731).
|
||||
_lastScrollTop=el.scrollTop;
|
||||
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||
if(snapshot.userUnpinned===true){
|
||||
_messageUserUnpinned=true;
|
||||
_scrollPinned=false;
|
||||
@@ -11068,7 +11068,7 @@ function _restoreMessageScrollSnapshotSameFrame(snapshot){
|
||||
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
||||
el.scrollTop=Math.max(0,Math.min(target,maxTop));
|
||||
}
|
||||
_lastScrollTop=el.scrollTop;
|
||||
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||
if(snapshot.pinned===true){
|
||||
_messageUserUnpinned=false;
|
||||
_scrollPinned=true;
|
||||
|
||||
@@ -18,6 +18,20 @@ REPO = pathlib.Path(__file__).resolve().parent.parent
|
||||
UI_JS = (REPO / "static" / "ui.js").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_client_height_seeded_with_scrolltop_on_programmatic_writes():
|
||||
"""Every programmatic seed of `_lastScrollTop` must also seed
|
||||
`_lastMessageClientHeight`, else the FIRST native toolbar-collapse scroll event
|
||||
sees a null/stale prior height, `grew` is false, and the false-unpin still
|
||||
fires (Codex gate finding). The two must always be written together."""
|
||||
# No bare `_lastScrollTop=el.scrollTop;` without the height co-seed remains.
|
||||
assert "_lastScrollTop=el.scrollTop;\n" not in UI_JS, (
|
||||
"A programmatic _lastScrollTop=el.scrollTop write is missing the paired "
|
||||
"_lastMessageClientHeight=el.clientHeight seed (#4702)."
|
||||
)
|
||||
# The paired form is present at the programmatic-write sites.
|
||||
assert UI_JS.count("_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;") >= 4
|
||||
|
||||
|
||||
def test_client_height_growth_guard_declared():
|
||||
"""The scroller-height tracker must be declared so a toolbar-settle reflow can
|
||||
be distinguished from a real user scroll."""
|
||||
|
||||
Reference in new issue
Block a user