From eee35f764f9e246169d563654e4103414684aa5d Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Fri, 28 Aug 2026 12:12:34 +0800 Subject: [PATCH] Refine transcript elasticity scope --- .../AI/ConversationTranscriptView.swift | 23 ---------- Termind/Features/AI/TranscriptListView.swift | 43 +------------------ TermindTests/TranscriptListViewTests.swift | 29 ------------- 3 files changed, 1 insertion(+), 94 deletions(-) diff --git a/Termind/Features/AI/ConversationTranscriptView.swift b/Termind/Features/AI/ConversationTranscriptView.swift index 0ffb8bfe..e2adf4e8 100644 --- a/Termind/Features/AI/ConversationTranscriptView.swift +++ b/Termind/Features/AI/ConversationTranscriptView.swift @@ -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( diff --git a/Termind/Features/AI/TranscriptListView.swift b/Termind/Features/AI/TranscriptListView.swift index 13378055..dd79663a 100644 --- a/Termind/Features/AI/TranscriptListView.swift +++ b/Termind/Features/AI/TranscriptListView.swift @@ -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()) { - $0.formUnion($1.toolCallIDs) - } - let newIDs = new.reduce(into: Set()) { - $0.formUnion($1.toolCallIDs) - } - return !newIDs.subtracting(oldIDs).isEmpty - } -} - -private extension TranscriptRenderItem { - nonisolated var toolCallIDs: Set { - 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()) { 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) } diff --git a/TermindTests/TranscriptListViewTests.swift b/TermindTests/TranscriptListViewTests.swift index f40b631d..9f3f5453 100644 --- a/TermindTests/TranscriptListViewTests.swift +++ b/TermindTests/TranscriptListViewTests.swift @@ -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) }