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