diff --git a/ios/Platform/Platform/Features/Reader/Views/ArticleView.swift b/ios/Platform/Platform/Features/Reader/Views/ArticleView.swift index 50977a0..aab286c 100644 --- a/ios/Platform/Platform/Features/Reader/Views/ArticleView.swift +++ b/ios/Platform/Platform/Features/Reader/Views/ArticleView.swift @@ -59,22 +59,35 @@ struct ArticleView: View { } } .task { - Task { await vm.markAsRead(entry) } + // 1. Synchronous local mutation — runs before any async work. + // The @Observable array mutation triggers ForEach row re-render + // immediately, so the list shows "read" even during push animation. + let entryId = entry.id + if let idx = vm.entries.firstIndex(where: { $0.id == entryId }), + !vm.entries[idx].isRead { + vm.entries[idx].status = "read" + } + currentEntry = vm.entries.first(where: { $0.id == entryId }) ?? currentEntry - if let updated = vm.entries.first(where: { $0.id == entry.id }) { - currentEntry = updated + // 2. API sync + counter refresh — background, fire-and-forget + Task { + let api = ReaderAPI() + try? await api.markEntries(ids: [entryId], status: "read") + vm.counters = try? await api.getCounters() } + // 3. Fetch full content — update when ready do { - let fullEntry = try await ReaderAPI().getEntry(id: entry.id) + let fullEntry = try await ReaderAPI().getEntry(id: entryId) let fullHTML = fullEntry.articleHTML if !fullHTML.isEmpty && fullHTML.count > articleContent.count { articleContent = fullHTML } + // Preserve local status/starred (may differ from server due to race) var merged = fullEntry - if let local = vm.entries.first(where: { $0.id == entry.id }) { + if let local = vm.entries.first(where: { $0.id == entryId }) { merged.status = local.status merged.starred = local.starred }