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;
|
if(!el) return;
|
||||||
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
||||||
el.scrollTop=el.scrollHeight;
|
el.scrollTop=el.scrollHeight;
|
||||||
_lastScrollTop=el.scrollTop;
|
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||||
_nearBottomCount=2;
|
_nearBottomCount=2;
|
||||||
_scrollPinned=true;
|
_scrollPinned=true;
|
||||||
requestAnimationFrame(()=>{
|
requestAnimationFrame(()=>{
|
||||||
@@ -4487,7 +4487,7 @@ function _setMessageScrollToBottom(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
el.scrollTop=el.scrollHeight;
|
el.scrollTop=el.scrollHeight;
|
||||||
_lastScrollTop=el.scrollTop;
|
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||||
_nearBottomCount=2;
|
_nearBottomCount=2;
|
||||||
_scrollPinned=true;
|
_scrollPinned=true;
|
||||||
_deferClearProgrammaticScroll();
|
_deferClearProgrammaticScroll();
|
||||||
@@ -4612,7 +4612,7 @@ function _settleFinalScroll(token){
|
|||||||
}
|
}
|
||||||
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
||||||
el.scrollTop=el.scrollHeight;
|
el.scrollTop=el.scrollHeight;
|
||||||
_lastScrollTop=el.scrollTop;
|
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||||
_nearBottomCount=2;
|
_nearBottomCount=2;
|
||||||
_scrollPinned=true;
|
_scrollPinned=true;
|
||||||
_deferClearProgrammaticScroll();
|
_deferClearProgrammaticScroll();
|
||||||
@@ -11002,7 +11002,7 @@ function _restoreMessageScrollSnapshot(snapshot){
|
|||||||
el.scrollTop=Math.max(0,Math.min(Number(snapshot.top)||0,maxTop));
|
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).
|
// 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){
|
if(snapshot.userUnpinned===true){
|
||||||
_messageUserUnpinned=true;
|
_messageUserUnpinned=true;
|
||||||
_scrollPinned=false;
|
_scrollPinned=false;
|
||||||
@@ -11068,7 +11068,7 @@ function _restoreMessageScrollSnapshotSameFrame(snapshot){
|
|||||||
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
_programmaticScroll=true;_programmaticScrollSetAt=performance.now();
|
||||||
el.scrollTop=Math.max(0,Math.min(target,maxTop));
|
el.scrollTop=Math.max(0,Math.min(target,maxTop));
|
||||||
}
|
}
|
||||||
_lastScrollTop=el.scrollTop;
|
_lastScrollTop=el.scrollTop;_lastMessageClientHeight=el.clientHeight;
|
||||||
if(snapshot.pinned===true){
|
if(snapshot.pinned===true){
|
||||||
_messageUserUnpinned=false;
|
_messageUserUnpinned=false;
|
||||||
_scrollPinned=true;
|
_scrollPinned=true;
|
||||||
|
|||||||
@@ -18,6 +18,20 @@ REPO = pathlib.Path(__file__).resolve().parent.parent
|
|||||||
UI_JS = (REPO / "static" / "ui.js").read_text(encoding="utf-8")
|
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():
|
def test_client_height_growth_guard_declared():
|
||||||
"""The scroller-height tracker must be declared so a toolbar-settle reflow can
|
"""The scroller-height tracker must be declared so a toolbar-settle reflow can
|
||||||
be distinguished from a real user scroll."""
|
be distinguished from a real user scroll."""
|
||||||
|
|||||||
Reference in new issue
Block a user