Merge nucleic/gentle-breezy-egret into dev
This commit is contained in:
@@ -42,15 +42,27 @@ struct TranscriptItem: Identifiable, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A row that draws nothing visible — an empty/redacted `.thinking` block (the agent streams
|
/// A row that draws nothing visible — an empty/redacted `.thinking` block (the agent streams
|
||||||
/// a finalized empty thinking item whenever the reasoning itself is redacted). Held aside
|
/// a finalized empty thinking item whenever the reasoning itself is redacted). `ThinkingRow`
|
||||||
/// during tool-run coalescing so it doesn't split an otherwise-contiguous run of calls into
|
/// renders no content for it, so `flatItems` drops it outright rather than carrying a phantom
|
||||||
/// two cards with a gap. Mirrors the desktop projection's `rendersNothing`.
|
/// row into the transcript.
|
||||||
var rendersNothing: Bool {
|
///
|
||||||
|
/// It has to be dropped, not merely rendered as an empty view: the transcript is a
|
||||||
|
/// `VStack(spacing: 14)` over `TranscriptRow`s, and a row whose body resolves to nothing is
|
||||||
|
/// still a *child* of that stack — SwiftUI can't elide an opaque `View` struct the way it
|
||||||
|
/// elides a literal `EmptyView`, so it still claims its 14pt inter-row gap. A long tool run
|
||||||
|
/// interleaved with redacted thinking (one empty block per call) held them all aside and
|
||||||
|
/// re-emitted them after the block, stacking one gap each into a large band of blank space
|
||||||
|
/// between the card and the next row. Mirrors the desktop projection's `isEmptyThinking`.
|
||||||
|
var isEmptyThinking: Bool {
|
||||||
if case .thinking(let text) = kind {
|
if case .thinking(let text) = kind {
|
||||||
return text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
return text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Items that draw no visible row. They must not break a run of tool calls when coalescing:
|
||||||
|
/// an invisible row sitting between two calls would otherwise split them into two cards.
|
||||||
|
var rendersNothing: Bool { isEmptyThinking }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One tool call's coalesced lifecycle: start → input deltas → complete → result → file changes.
|
/// One tool call's coalesced lifecycle: start → input deltas → complete → result → file changes.
|
||||||
@@ -409,10 +421,13 @@ enum TranscriptProjection {
|
|||||||
|
|
||||||
/// Collapses maximal runs of adjacent `.tool` items (length ≥2) into one `.toolBlock`.
|
/// Collapses maximal runs of adjacent `.tool` items (length ≥2) into one `.toolBlock`.
|
||||||
///
|
///
|
||||||
/// Items that draw nothing (empty `.thinking` blocks — see `rendersNothing`) never break a
|
/// Items that draw nothing (see `rendersNothing`) never break a run: an invisible row landing
|
||||||
/// run: an invisible row landing between two tool calls must not split them into two cards
|
/// between two tool calls must not split them into two cards with a gap. Such items are held
|
||||||
/// with a gap. Such items are held aside and re-emitted right after the block, so they still
|
/// aside and re-emitted right after the block, so the calls stay one contiguous block.
|
||||||
/// render in place while the calls stay one contiguous block. Mirrors the desktop projection.
|
/// Mirrors the desktop projection.
|
||||||
|
///
|
||||||
|
/// `flatItems` already drops the only kind the phone has (empty redacted thinking), so the
|
||||||
|
/// hold path is a guard for a caller passing unfiltered items — not a live case.
|
||||||
static func coalesceToolRuns(_ flat: [TranscriptItem]) -> [TranscriptItem] {
|
static func coalesceToolRuns(_ flat: [TranscriptItem]) -> [TranscriptItem] {
|
||||||
var out: [TranscriptItem] = []
|
var out: [TranscriptItem] = []
|
||||||
var run: [TranscriptItem] = [] // consecutive `.tool` items
|
var run: [TranscriptItem] = [] // consecutive `.tool` items
|
||||||
@@ -510,7 +525,12 @@ enum TranscriptProjection {
|
|||||||
kind: .raw(type: event.nativeType ?? "raw", body: raw.native.compactSummary)))
|
kind: .raw(type: event.nativeType ?? "raw", body: raw.native.compactSummary)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return items
|
// A redacted (empty) thinking block draws no row but would still cost one inter-row gap
|
||||||
|
// in the transcript's spaced VStack — see `isEmptyThinking`. Drop it here, after the
|
||||||
|
// coalescing upserts, so a *streaming* thinking item is judged on its accumulated text
|
||||||
|
// rather than on the empty first delta. A thinking message never straddles a segment
|
||||||
|
// seam (`buildSegment`'s precondition), so this stays fold-equivalent per segment.
|
||||||
|
return items.filter { !$0.isEmptyThinking }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Coalescing helpers
|
// MARK: - Coalescing helpers
|
||||||
|
|||||||
Reference in New Issue
Block a user