From cbd155fc874167dd9339bd303fb3aab3d88653b7 Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Mon, 6 Jul 2026 03:32:34 -0700 Subject: [PATCH] Session Chat Auto-Scroll Adjustment Nucleic-Session: 8FCEB1B7-A6C0-44E2-B00A-E4786F41A1FA Co-authored-by: Nucleic --- .../NucleicRemote/Views/SessionDetailView.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift index 84bfdd8..c1d5d9e 100644 --- a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift +++ b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift @@ -442,10 +442,22 @@ struct TranscriptList: View { var body: some View { ScrollViewReader { proxy in ScrollView { - LazyVStack(alignment: .leading, spacing: 14) { + // Eager VStack (not Lazy): the whole transcript is in memory, and eager layout + // hands the ScrollView its true, *fixed* total content height up front. A + // LazyVStack instead realizes rows as they scroll into view, so its reported + // content height shifts mid-scroll as rows resolve — and that shifting height fed + // straight back through the geometry reader below (flip at-bottom → toggle the + // `.defaultScrollAnchor` → re-pin → new height → …), which is what made the whole + // transcript jitter up and down under a drag. A stable height breaks the loop. + // Matches the Mac transcript, for the same reason. + VStack(alignment: .leading, spacing: 14) { ForEach(items) { item in TranscriptRow(item: item).id(item.id) } + // Fixed, zero-content scroll target for "jump to bottom". Scrolling to the last + // row's id instead would chase a moving target — the final row's identity + // changes as its streaming deltas coalesce — nudging the viewport mid-stream. + Color.clear.frame(height: 1).id(Self.bottomAnchorID) } .padding() // Cap the transcript to a readable measure on a wide iPad so lines don't run