From 5021783f7ac9c3ea3be5e49c932a76cba62f1865 Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Mon, 6 Jul 2026 19:27:30 -0700 Subject: [PATCH] nvrsion: Add: Move the chevron circle vertically above the composer bar and fix the jump distance so the chevron button is hidden when tapped. Nucleic-Promote: 1 Co-authored-by: Nucleic --- .../Views/SessionDetailView.swift | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift index 33e7a30..678c49b 100644 --- a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift +++ b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift @@ -306,9 +306,11 @@ struct SessionDetailView: View { .overlay(alignment: .top) { if !isScrolledToBottom { JumpToBottomButton { scrollToBottomRequest += 1 } - // Hang the button *above* the bar: report its bottom edge (plus a gap) as its - // top guide, so the overlay's top alignment places it outside the bar's bounds. - .alignmentGuide(.top) { $0[.bottom] + 8 } + // Hang the button fully *above* the bar: top-aligned, then shifted up by its + // own 30pt height plus an 8pt gap. A render-time offset (not an alignment + // guide, which misplaced it half-overlapping the bar's top edge) so the + // placement is exact and — critically — contributes nothing to the inset. + .offset(y: -38) .transition(.move(edge: .bottom).combined(with: .opacity)) } } @@ -705,11 +707,18 @@ struct TranscriptList: View { 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, - // so it sits over the composer instead of behind the floating bar; a tap bumps this - // same request, and following resumes once the geometry reader sees the bottom. - .onChange(of: scrollToBottomRequest) { scrollToEnd(proxy) } + // An explicit jump — the chevron or sending a message — always wins, and it *is* the + // user saying "follow again": re-engage the gate directly rather than waiting for the + // geometry to confirm. The animated scroll can come to rest a few points short of the + // follow threshold (content padding, inset rounding), which sat inside the hysteresis + // band — following never re-engaged and the chevron lingered until the user manually + // scrolled the last few points. Setting the gate here hides the chevron immediately + // and hands pinning back to the bottom anchor; a later real scroll-up still + // disengages it as usual. + .onChange(of: scrollToBottomRequest) { + isScrolledToBottom = true + scrollToEnd(proxy) + } // Warm the Markdown parse caches off the main thread whenever the row set grows — // `initial: true` fires it for the batch that lands on open (the expensive case), and // each later new row tops it up. Keyed on the row *count*, so streaming deltas into an