Merge nucleic/plucky-fern-dingo into dev
Chat: open existing sessions already scrolled to the bottom (no scroll animation) via .defaultScrollAnchor(.bottom).
This commit is contained in:
@@ -15,6 +15,13 @@ struct SessionDetailView: View {
|
||||
@State private var summaryExpanded = false
|
||||
/// The user message awaiting a "revert to here" confirmation (its canonical `seq`).
|
||||
@State private var revertSeq: UInt64?
|
||||
/// Set when a session is opened; the next transcript load jumps to the bottom
|
||||
/// without animation so an existing chat appears already scrolled down. The
|
||||
/// transcript loads asynchronously after `openSessionID` changes, so the
|
||||
/// instant jump has to ride the first content arrival, not the id change.
|
||||
/// Starts armed so the first navigation into a chat (which creates this view
|
||||
/// fresh, with no `openSessionID` change to observe) also lands at the bottom.
|
||||
@State private var pendingOpenScroll = true
|
||||
@AppStorage(TranscriptDisplay.showDebugKey) private var showDebugLines = false
|
||||
@AppStorage(SubmitKeyMode.storageKey) private var submitKeyRaw = SubmitKeyMode.returnSends.rawValue
|
||||
|
||||
@@ -394,9 +401,25 @@ struct SessionDetailView: View {
|
||||
.padding(.top, 14)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
// Anchor initial layout at the bottom so an opened chat renders already
|
||||
// scrolled down — without this the ScrollView lays out at the top first,
|
||||
// producing a visible top-then-scroll flash on open.
|
||||
.defaultScrollAnchor(.bottom)
|
||||
.scrollContentBackground(.hidden)
|
||||
.onChange(of: store.openTranscript.count) { _, _ in scrollToEnd(proxy) }
|
||||
.onChange(of: store.openSessionID) { _, _ in scrollToEnd(proxy) }
|
||||
.onChange(of: store.openTranscript.count) { _, _ in
|
||||
// The first load after opening jumps instantly; later appends (new
|
||||
// content while reading) animate as usual.
|
||||
if pendingOpenScroll {
|
||||
pendingOpenScroll = false
|
||||
scrollToEnd(proxy, animated: false)
|
||||
} else {
|
||||
scrollToEnd(proxy)
|
||||
}
|
||||
}
|
||||
// Opening a session should land already scrolled down, not scroll into
|
||||
// place after the fact. The transcript loads asynchronously, so arm the
|
||||
// flag here and let the first content arrival do the instant jump.
|
||||
.onChange(of: store.openSessionID) { _, _ in pendingOpenScroll = true }
|
||||
.onChange(of: isBusy) { _, busy in if busy { scrollToEnd(proxy) } }
|
||||
}
|
||||
// The visible summary floats in the top-right corner over the transcript;
|
||||
@@ -415,10 +438,14 @@ struct SessionDetailView: View {
|
||||
private static let workingID = "nucleic.working-indicator"
|
||||
private static let bottomAnchorID = "nucleic.transcript-bottom"
|
||||
|
||||
private func scrollToEnd(_ proxy: ScrollViewProxy) {
|
||||
private func scrollToEnd(_ proxy: ScrollViewProxy, animated: Bool = true) {
|
||||
// Always scroll to the trailing spacer, so the resting position shows the
|
||||
// last message with the full bottom padding beneath it.
|
||||
withAnimation {
|
||||
if animated {
|
||||
withAnimation {
|
||||
proxy.scrollTo(Self.bottomAnchorID, anchor: .bottom)
|
||||
}
|
||||
} else {
|
||||
proxy.scrollTo(Self.bottomAnchorID, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user