Refine transcript elasticity scope
This commit is contained in:
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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user