From db50dfbc361477c968f5053cea84c7845f458650 Mon Sep 17 00:00:00 2001 From: Ashlee Radka Date: Wed, 25 Feb 2026 20:33:56 -0500 Subject: [PATCH] fix: populate cachedInputFull on expand via onChange to prevent flash Co-Authored-By: Claude --- .../Features/Chat/UsedToolsList.swift | 13 +++++++++++++ clients/shared/Features/Chat/ToolCallChip.swift | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/clients/macos/vellum-assistant/Features/Chat/UsedToolsList.swift b/clients/macos/vellum-assistant/Features/Chat/UsedToolsList.swift index bbdf43d5f1c..afceabbace0 100644 --- a/clients/macos/vellum-assistant/Features/Chat/UsedToolsList.swift +++ b/clients/macos/vellum-assistant/Features/Chat/UsedToolsList.swift @@ -317,6 +317,19 @@ private struct UsedToolsRow: View { } } .animation(VAnimation.fast, value: isExpanded) + .onChange(of: isExpanded) { newValue in + // Populate the cache *before* the expanded body evaluates so that + // `resolvedInputFull` returns the formatted input on the very first + // render of the expanded section — avoiding a visible flash/pop-in + // for lazy-loaded history tool calls where `.onAppear` fires too late. + if newValue, cachedInputFull == nil { + if let dict = toolCall.inputRawDict { + cachedInputFull = ToolCallData.formatAllToolInput(dict) + } else if !toolCall.inputFull.isEmpty { + cachedInputFull = toolCall.inputFull + } + } + } .onChange(of: toolCall.inputFull) { _ in // Invalidate the cached formatted input so the next render picks up // the fresh (rehydrated) value instead of the stale truncated one. diff --git a/clients/shared/Features/Chat/ToolCallChip.swift b/clients/shared/Features/Chat/ToolCallChip.swift index 8177a49bb7f..c402bdc057b 100644 --- a/clients/shared/Features/Chat/ToolCallChip.swift +++ b/clients/shared/Features/Chat/ToolCallChip.swift @@ -200,6 +200,19 @@ public struct ToolCallChip: View { ? VColor.error.opacity(0.3) : VColor.surfaceBorder.opacity(0.5), lineWidth: 0.5) ) + .onChange(of: isExpanded) { newValue in + // Populate the cache *before* the expanded body evaluates so that + // `resolvedInputFull` returns the formatted input on the very first + // render of the expanded section — avoiding a visible flash/pop-in + // for lazy-loaded history tool calls where `.onAppear` fires too late. + if newValue, cachedInputFull == nil { + if let dict = toolCall.inputRawDict { + cachedInputFull = ToolCallData.formatAllToolInput(dict) + } else if !toolCall.inputFull.isEmpty { + cachedInputFull = toolCall.inputFull + } + } + } .onChange(of: toolCall.inputFull) { _ in // Invalidate the cached formatted input so the next render picks up // the fresh (rehydrated) value instead of the stale truncated one.