nvrsion: Add: Investigate Live Activity Session Load logic in SessionDetailView.swift and investigate why Live Activity Session Load doesn’t always trigger when a session is opened; add Stop Button Implementation in iOS app’s composer.

Nucleic-Promote: 1
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-06 17:38:29 -07:00
co-authored by Nucleic
parent 37a80ac9bd
commit bce3da8d6f
@@ -31,18 +31,20 @@ struct SessionDetailView: View {
}
var body: some View {
// The detail's height comes from the enclosing GeometryReader — a value fixed by the parent
// (the navigation content area), never by anything inside it. The attention cards (approval /
// question) cap themselves to this so their buttons stay on-screen. Sourcing it from a
// GeometryReader — rather than measuring the transcript, whose height the cards' own bottom
// `safeAreaInset` resizes — makes the measurement categorically independent of the card, so
// the card can't feed its height back into the value it's sized from. That feedback was an
// unresolved layout cycle that pinned the main thread at 100% CPU during the push transition;
// it showed up intermittently because a feedback loop only diverges for some content/card-
// height combinations. It bit only the attention path (a card consumes this value; the plain
// composer doesn't), which is why a "Working" tap opened fine and an "attention" tap hung.
GeometryReader { proxy in
TranscriptList(events: store.openEvents,
scrollToBottomRequest: scrollToBottomRequest,
isScrolledToBottom: $isScrolledToBottom)
// Measure the transcript's full height (the floating bar overlays it via safeAreaInset,
// so this frame is the whole screen area the card has to live within) and hand it to the
// action card so it can bound itself.
.background {
GeometryReader { proxy in
Color.clear.preference(key: AvailableHeightKey.self, value: proxy.size.height)
}
}
.onPreferenceChange(AvailableHeightKey.self) { availableHeight = $0 }
// The chat bar floats over the scrolling content on Liquid Glass instead of sitting
// in a boxed strip below it, so the transcript runs the full height of the screen.
.safeAreaInset(edge: .bottom) { actionArea }
@@ -81,6 +83,14 @@ struct SessionDetailView: View {
// disappears) unsubscribes A without tearing down B's just-opened state.
.onDisappear { store.closeOpen(sessionID) }
.background { interruptShortcut }
// Publish the parent-determined height to the cards. `initial: true` seeds it on the
// first layout; it refreshes if the container resizes (rotation, keyboard, iPad split
// resize). Because `proxy.size.height` never depends on the card, updating this can't
// re-drive the measurement — no cycle.
.onChange(of: proxy.size.height, initial: true) { _, height in
availableHeight = height
}
}
}
/// ⌘. interrupts a running session (the Mac's "stop" convention) — the action is otherwise
@@ -352,6 +362,18 @@ struct SessionDetailView: View {
.textFieldStyle(.plain)
.lineLimit(1...4)
.padding(.vertical, 3)
// Stop the in-flight turn (the Mac's ⌘. / "Interrupt"). Shown only
// while running and at control scope; send stays at the far right so
// its position never shifts. Mirrors `interruptShortcut`.
if running && store.canControl {
Button { store.interrupt(sessionID) } label: {
Image(systemName: "stop.circle.fill")
.font(.title2)
.foregroundStyle(.red)
}
.disabled(!store.connectivity.isLive)
.accessibilityLabel("Stop")
}
Button {
store.sendInput(draft, to: sessionID)
draft = ""
@@ -448,15 +470,6 @@ struct SessionDetailView: View {
}
}
/// The transcript's measured height, fed to the floating action card so it can bound itself to the
/// screen (see `availableHeight`).
private struct AvailableHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
struct TranscriptList: View {
let events: [AgentEvent]
/// Bumped by the parent when the user sends a message — a deliberate "show me what happens