From a757192bd71e87e02ec19317a5a234498f29661a Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Mon, 6 Jul 2026 19:00:35 -0700 Subject: [PATCH] Transcript Incremental Projection Nucleic-Session: 0FFD007B-0696-4517-9429-129C7B0FD5AC Co-authored-by: Nucleic --- .../NucleicRemote/Views/SessionDetailView.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift index c7f0f63..75c1d5d 100644 --- a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift +++ b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift @@ -683,9 +683,15 @@ struct TranscriptList: View { } // Coalescing means item count lags event count; key the autoscroll on the raw stream // so every streamed delta keeps the view pinned to the bottom — but only while the - // user is already parked there. Scrolling up to read history is never yanked down. + // user is already parked there. Scrolling up to read history is never yanked down, + // and a finger actively on the transcript is never fought mid-drag (the drag that + // takes them past the unfollow threshold flips the gate; until then the native + // bottom anchor alone keeps content pinned, without an animated scroll grabbing the + // viewport back out of their hand). .onChange(of: events.count) { - if isScrolledToBottom { scrollToEnd(proxy, animated: !transcriptSettling) } + if isScrolledToBottom, !isUserScrolling { + scrollToEnd(proxy, animated: !transcriptSettling) + } } // An explicit jump — the chevron or sending a message — always wins. The chevron // itself lives in the parent's chat bar (above the composer), not as an overlay here,