@@ -116,6 +116,9 @@ final class ViewModel: ObservableObject {
116116 /// instead of the first click's reset firing early.
117117 private var copiedResetTask: Task<Void, Never>?
118118
119+ /// Resets `savedConfirmation`; same rationale as `copiedResetTask`.
120+ private var savedResetTask: Task<Void, Never>?
121+
119122 // MARK: - History Configuration
120123
121124 /// 24 hours at the shortest (1 minute) interval would overflow this, so the
@@ -143,6 +146,13 @@ final class ViewModel: ObservableObject {
143146 return formatter
144147 }()
145148
149+ /// Cached relative time formatter for "Updated Xm ago" text.
150+ private static let relativeFormatter: RelativeDateTimeFormatter = {
151+ let formatter = RelativeDateTimeFormatter()
152+ formatter.unitsStyle = .short
153+ return formatter
154+ }()
155+
146156 /// Formats an integer with thousands grouping separator.
147157 private func formatBalance(_ value: Int) -> String {
148158 Self.balanceFormatter.string(from: NSNumber(value: value)) ?? "\(value)"
@@ -176,9 +186,7 @@ final class ViewModel: ObservableObject {
176186 /// Relative time string for the last successful update, or nil if never updated.
177187 var lastUpdatedText: String? {
178188 guard let lastUpdated = lastUpdated else { return nil }
179− let formatter = RelativeDateTimeFormatter()
180− formatter.unitsStyle = .short
181− return "Updated \(formatter.localizedString(for: lastUpdated, relativeTo: Date()))"
189+ return "Updated \(Self.relativeFormatter.localizedString(for: lastUpdated, relativeTo: Date()))"
182190 }
183191
184192 // MARK: - History
@@ -216,16 +224,18 @@ final class ViewModel: ObservableObject {
216224 /// Refreshes the balance from the API. No-op if no API key is set.
217225 /// Cancels any previously in-flight refresh to prevent races.
218226 func refresh() {
227+ refreshTask?.cancel()
228+
219229 let key = apiKeyInput.trimmingCharacters(in: .whitespacesAndNewlines)
220230 guard !key.isEmpty else {
221231 balance = nil
222232 errorMessage = nil
223233 lastUpdated = nil
234+ history.removeAll()
235+ isLoading = false
224236 return
225237 }
226238
227− refreshTask?.cancel()
228−
229239 refreshTask = Task { @MainActor in
230240 isLoading = true
231241 // Don't clear errorMessage here — keep showing the last error
@@ -312,22 +322,29 @@ final class ViewModel: ObservableObject {
312322 let key = apiKeyInput.trimmingCharacters(in: .whitespacesAndNewlines)
313323 if key.isEmpty {
314324 KeychainHelper.delete()
315− balance = nil
316− errorMessage = nil
317− lastUpdated = nil
325+ // refresh() with an empty key clears balance, error, history, and
326+ // cancels any in-flight task from the previous key.
327+ refresh()
318328 } else {
319329 let saved = KeychainHelper.save(key)
320330 guard saved else {
321331 errorMessage = "Could not save API key to Keychain. It may be locked."
322332 return
323333 }
334+ // Clear history from any previous key so the sparkline and trend
335+ // don't mix data from different accounts.
336+ history.removeAll()
324337 refresh()
325338 }
326339
327− // Show "✓ Saved" confirmation, auto-reset after 2 seconds
340+ // Show "✓ Saved" confirmation, auto-reset after 2 seconds.
341+ // Cancel any previous reset so rapid Save clicks don't hide the
342+ // confirmation prematurely.
328343 savedConfirmation = true
329− Task { @MainActor in
344+ savedResetTask?.cancel()
345+ savedResetTask = Task { @MainActor in
330346 try? await Task.sleep(nanoseconds: 2_000_000_000)
347+ guard !Task.isCancelled else { return }
331348 savedConfirmation = false
332349 }
333350 }