Transcript Incremental Projection
Nucleic-Session: 0FFD007B-0696-4517-9429-129C7B0FD5AC Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -633,22 +633,28 @@ struct TranscriptList: View {
|
|||||||
// they scroll away, which lets new content land off-screen below instead of dragging
|
// they scroll away, which lets new content land off-screen below instead of dragging
|
||||||
// the viewport, and restore `.bottom` once they're back at the end.
|
// the viewport, and restore `.bottom` once they're back at the end.
|
||||||
.defaultScrollAnchor(isScrolledToBottom ? .bottom : nil)
|
.defaultScrollAnchor(isScrolledToBottom ? .bottom : nil)
|
||||||
|
.onScrollPhaseChange { _, newPhase in scrollPhase = newPhase }
|
||||||
// Track the live scroll position straight from the scroll view's geometry: how much
|
// Track the live scroll position straight from the scroll view's geometry: how much
|
||||||
// content still sits below the viewport bottom. Within the slack threshold means the
|
// content still sits below the viewport bottom. Parked within the follow threshold
|
||||||
// user is parked at the end (live output keeps following); scrolling up flips this
|
// means live output keeps following; a *user* scroll past the (wider) unfollow
|
||||||
// false, which drops the anchor (above) and reveals the jump-to-bottom chevron.
|
// threshold flips it false, which drops the anchor (above) and reveals the chevron.
|
||||||
.onScrollGeometryChange(for: Bool.self) { geo in
|
// Asymmetric on purpose — see `bottomUnfollowThreshold` / `isUserScrolling`: a
|
||||||
geo.contentSize.height - geo.containerSize.height - geo.contentOffset.y
|
// programmatic geometry change (anchor re-pin, bar resize, keyboard, autoscroll
|
||||||
<= bottomFollowThreshold
|
// animation) may re-engage following but can never disengage it, so no layout
|
||||||
} action: { _, atBottom in
|
// feedback cycle through this gate can sustain itself. Tracking the rounded distance
|
||||||
// While the chat is opening the layout grows over a few passes and the scroll
|
// (not a Bool) also means every scroll emits fresh values, so the gate can't latch
|
||||||
// offset lags each growth by a frame — sampling that frame reads "not at bottom"
|
// against a stale reading (the old settle-window latch bug).
|
||||||
// even though `.defaultScrollAnchor(.bottom)` is about to re-pin. So during the
|
.onScrollGeometryChange(for: CGFloat.self) { geo in
|
||||||
// open window accept only "at bottom" readings; honor real scroll-ups after.
|
(geo.contentSize.height - geo.containerSize.height - geo.contentOffset.y).rounded()
|
||||||
if transcriptSettling {
|
} action: { _, distance in
|
||||||
if atBottom { isScrolledToBottom = true }
|
if distance <= bottomFollowThreshold {
|
||||||
} else {
|
isScrolledToBottom = true
|
||||||
isScrolledToBottom = atBottom
|
} else if !transcriptSettling, distance > bottomUnfollowThreshold, isUserScrolling {
|
||||||
|
// While the chat is opening the layout grows over a few passes and the offset
|
||||||
|
// lags each growth by a frame — those frames read "not at bottom" even though
|
||||||
|
// `.defaultScrollAnchor(.bottom)` is about to re-pin, so settling accepts
|
||||||
|
// only re-engagement (the `!transcriptSettling` above).
|
||||||
|
isScrolledToBottom = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Detect when the opening layout has come to rest: track the content height while
|
// Detect when the opening layout has come to rest: track the content height while
|
||||||
|
|||||||
Reference in New Issue
Block a user