This commit is contained in:
@@ -30,7 +30,6 @@ private struct AgentTranscriptView: View {
|
||||
@State private var historyStartID: String?
|
||||
@State private var scrollPosition = ScrollPosition()
|
||||
@State private var scrollTracking = AgentScrollTracking()
|
||||
@State private var restoreAnchor: AgentReadingAnchor?
|
||||
@State private var appeared = false
|
||||
private let bottomID = "agent-bottom"
|
||||
|
||||
@@ -51,16 +50,12 @@ private struct AgentTranscriptView: View {
|
||||
let displayed = transcript.displayed
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView {
|
||||
// Exact eager geometry preserves reading/prepend offsets. Lazy
|
||||
// estimates required repeated corrections and failed those anchors;
|
||||
// isolate observation instead of adding a geometry feedback loop.
|
||||
VStack(alignment: .leading, spacing: 15) {
|
||||
LazyVStack(alignment: .leading, spacing: 15) {
|
||||
if allMessages.count > displayed.count {
|
||||
Button("Load earlier messages") {
|
||||
if var anchor = visibleReadingAnchor {
|
||||
anchor.expectedCount = min(allMessages.count, displayed.count + 100)
|
||||
restoreAnchor = anchor
|
||||
}
|
||||
// Loading older history may move the viewport as lazy
|
||||
// row estimates settle; do not chase the old position.
|
||||
scrollTracking.rowFrames = [:]
|
||||
atLatest = false
|
||||
historyLimit += 100
|
||||
historyStartID = allMessages[max(0, startIndex - 100)].id
|
||||
@@ -104,7 +99,6 @@ private struct AgentTranscriptView: View {
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.onScrollPhaseChange { _, phase in
|
||||
isScrolling = phase.isScrolling
|
||||
if phase == .interacting || phase == .tracking { restoreAnchor = nil }
|
||||
}
|
||||
.onScrollGeometryChange(for: AgentScrollMetrics.self) { geometry in
|
||||
AgentScrollMetrics(offset: geometry.contentOffset.y,
|
||||
@@ -119,7 +113,7 @@ private struct AgentTranscriptView: View {
|
||||
scrollTracking.metrics = next
|
||||
if scrollTracking.atBottom != next.atBottom { scrollTracking.atBottom = next.atBottom }
|
||||
if isScrolling || next.contentHeight <= next.height { atLatest = next.atBottom }
|
||||
if atLatest, !isScrolling, restoreAnchor == nil,
|
||||
if atLatest, !isScrolling,
|
||||
previous.height != next.height || previous.contentHeight != next.contentHeight || previous.bottomInset != next.bottomInset {
|
||||
proxy.scrollTo(bottomID, anchor: .bottom)
|
||||
}
|
||||
@@ -128,10 +122,10 @@ private struct AgentTranscriptView: View {
|
||||
#if DEBUG
|
||||
AgentRenderDiagnostics.record("rowPreferences")
|
||||
#endif
|
||||
let anchor = restoreAnchor ?? (!atLatest && !isScrolling ? visibleReadingAnchor : nil)
|
||||
let anchor = !atLatest && !isScrolling ? visibleReadingAnchor : nil
|
||||
if let anchor, let frame = frames[anchor.id], abs(frame.minY - anchor.offset) > 1 {
|
||||
// Restore the exact pixel offset after insertion or text
|
||||
// reflow. Normalize the saved frames to
|
||||
// Preserve ordinary reading position after new messages or
|
||||
// text reflow. Normalize the saved frames to
|
||||
// the requested offset so the next layout cannot undo it.
|
||||
let requested = scrollTracking.metrics.offset + frame.minY - anchor.offset
|
||||
let target = min(max(requested, -scrollTracking.metrics.topInset), scrollTracking.metrics.maximumOffset)
|
||||
@@ -142,16 +136,13 @@ private struct AgentTranscriptView: View {
|
||||
// zero at the top inset, including the safe area.
|
||||
scrollPosition.scrollTo(y: target + scrollTracking.metrics.topInset)
|
||||
}
|
||||
restoreAnchor = nil
|
||||
} else {
|
||||
scrollTracking.rowFrames = frames
|
||||
if let expected = restoreAnchor?.expectedCount, frames.count >= expected { restoreAnchor = nil }
|
||||
}
|
||||
}
|
||||
.overlay(alignment: .bottomTrailing) {
|
||||
if !atLatest {
|
||||
Button {
|
||||
restoreAnchor = nil
|
||||
atLatest = true
|
||||
withAnimation(reduceMotion ? nil : .easeOut(duration: 0.24)) { proxy.scrollTo(bottomID, anchor: .bottom) }
|
||||
} label: {
|
||||
@@ -176,7 +167,6 @@ private struct AgentTranscriptView: View {
|
||||
if atLatest { proxy.scrollTo(bottomID, anchor: .bottom) }
|
||||
}
|
||||
.onChange(of: latestRequest) { _, _ in
|
||||
restoreAnchor = nil
|
||||
atLatest = true
|
||||
proxy.scrollTo(bottomID, anchor: .bottom)
|
||||
}
|
||||
@@ -407,7 +397,6 @@ private struct AgentScrollMetrics: Equatable {
|
||||
private struct AgentReadingAnchor {
|
||||
var id: String
|
||||
var offset: CGFloat
|
||||
var expectedCount: Int? = nil
|
||||
}
|
||||
private struct AgentRowFrames: PreferenceKey {
|
||||
static let defaultValue: [String: CGRect] = [:]
|
||||
|
||||
Reference in New Issue
Block a user