Refine transcript elasticity scope

This commit is contained in:
Kaede Akino committed 2026-08-28 16:19:32 +08:00
1 parent e26f79fd65
commit eee35f764f
3 files changed
+1 -94

No files matched your search

@@ -281,7 +281,6 @@ private struct AgentWorkGroupView: View {
let onDisclosureToggle: () -> Void
@Environment(\.locale) private var locale
@Environment(\.accessibilityReduceMotion) private var reduceMotion
private var defaultExpanded: Bool {
AgentWorkPresentation.isExpandedByDefault(
@@ -389,39 +388,17 @@ private struct AgentWorkGroupView: View {
card: card, presentation: presentation,
onDisclosureToggle: onDisclosureToggle
)
.transition(toolInsertionTransition)
case .tools(let batch):
AgentToolBatchView(
batch: batch, presentation: presentation,
onDisclosureToggle: onDisclosureToggle
)
.transition(toolInsertionTransition)
}
}
}
.animation(
reduceMotion ? nil : .easeOut(duration: 0.18),
value: toolCallIDs
)
.frame(maxWidth: .infinity, alignment: .leading)
}
private var toolCallIDs: [UUID] {
group.items.flatMap { item in
switch item {
case .tool(let card): return [card.id]
case .tools(let batch): return batch.cards.map(\.id)
case .reasoning, .message: return []
}
}
}
private var toolInsertionTransition: AnyTransition {
reduceMotion
? .identity
: .opacity.combined(with: .move(edge: .top))
}
/// Reasoning uses MarkdownView's streaming path but never gains terminal
/// actions; it is narration, not a suggested command.
private func reasoningBlock(
+1 -42
View File
@@ -174,41 +174,6 @@ nonisolated struct TranscriptListUpdatePlan: Equatable, Sendable {
)
}
/// Tool calls are nested inside a stable work-group row, so an ordinary
/// high-frequency `update` would reveal them with no structural motion.
/// Detect only newly introduced calls: state/progress/token revisions stay
/// on the inexpensive non-animated path.
static func introducesToolCalls(
old: [TranscriptRenderItem], new: [TranscriptRenderItem]
) -> Bool {
let oldIDs = old.reduce(into: Set<UUID>()) {
$0.formUnion($1.toolCallIDs)
}
let newIDs = new.reduce(into: Set<UUID>()) {
$0.formUnion($1.toolCallIDs)
}
return !newIDs.subtracting(oldIDs).isEmpty
}
}
private extension TranscriptRenderItem {
nonisolated var toolCallIDs: Set<UUID> {
guard case .timeline(let item) = content else { return [] }
switch item {
case .tool(let card):
return [card.id]
case .work(let group):
return group.items.reduce(into: Set<UUID>()) { result, item in
switch item {
case .tool(let card): result.insert(card.id)
case .tools(let batch): result.formUnion(batch.cards.map(\.id))
case .reasoning, .message: break
}
}
case .message, .plan, .compaction, .compactionActivity:
return []
}
}
}
/// SwiftUI ownership outside, AppKit virtualization inside. The representable
@@ -306,13 +271,7 @@ struct TranscriptListView: NSViewRepresentable {
&& !listView.isUserInteractingWithScroll)
let plan = TranscriptListUpdatePlan.make(old: content, new: newItems)
let animatesToolInsertion = !isFirstUpdate
&& TranscriptListUpdatePlan.introducesToolCalls(
old: content, new: newItems
)
if animatesToolInsertion {
listView.apply(newItems, animated: true)
} else if plan.replacesAll {
if plan.replacesAll {
listView.apply(newItems)
} else {
for item in plan.updates { listView.update(item) }
@@ -78,35 +78,6 @@ struct TranscriptListUpdatePlanTests {
#expect(plan.appends == [new[1]])
}
@Test
func onlyANewToolCallRequestsStructuralAnimation() {
let card = AgentToolCard(
invocation: AgentToolInvocation(
runID: UUID(),
call: AgentToolCall(id: "tool-1", name: "read", arguments: .object([:]))
),
descriptor: AgentToolDescriptor(
name: "read", summary: "Read", inputSchema: .object([:]),
target: .local, safety: .locallyReadOnly
)
)
let proposed = TranscriptRenderItem(
id: card.id, content: .timeline(.tool(card))
)
var runningCard = card
runningCard.state = .running
let running = TranscriptRenderItem(
id: runningCard.id, content: .timeline(.tool(runningCard))
)
#expect(TranscriptListUpdatePlan.introducesToolCalls(
old: [], new: [proposed]
))
#expect(!TranscriptListUpdatePlan.introducesToolCalls(
old: [proposed], new: [running]
))
}
private func item(_ value: UInt8) -> TranscriptRenderItem {
TranscriptRenderItem(id: uuid(value), content: .empty)
}