debug: timestamped logging for first-article-open stall investigation
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s

This commit is contained in:
Yusuf Suleman
2026-04-04 00:35:53 -05:00
parent 7938034d85
commit 62f9a2503a
2 changed files with 28 additions and 8 deletions

View File

@@ -13,6 +13,7 @@ struct ArticleView: View {
self.vm = vm self.vm = vm
_currentEntry = State(initialValue: entry) _currentEntry = State(initialValue: entry)
_articleContent = State(initialValue: entry.articleHTML) _articleContent = State(initialValue: entry.articleHTML)
print("[ART-OPEN] init contentLen=\(entry.articleHTML.count)")
} }
var body: some View { var body: some View {
@@ -59,33 +60,37 @@ struct ArticleView: View {
} }
} }
.task { .task {
// 1. Synchronous local mutation runs before any async work. let t0 = CFAbsoluteTimeGetCurrent()
// The @Observable array mutation triggers ForEach row re-render print("[ART-OPEN] .task started")
// immediately, so the list shows "read" even during push animation.
let entryId = entry.id let entryId = entry.id
if let idx = vm.entries.firstIndex(where: { $0.id == entryId }), if let idx = vm.entries.firstIndex(where: { $0.id == entryId }),
!vm.entries[idx].isRead { !vm.entries[idx].isRead {
vm.entries[idx].status = "read" vm.entries[idx].status = "read"
} }
currentEntry = vm.entries.first(where: { $0.id == entryId }) ?? currentEntry currentEntry = vm.entries.first(where: { $0.id == entryId }) ?? currentEntry
print("[ART-OPEN] mark-read done +\(Int((CFAbsoluteTimeGetCurrent()-t0)*1000))ms")
// 2. API sync + counter refresh background, fire-and-forget
Task { Task {
let api = ReaderAPI() let api = ReaderAPI()
try? await api.markEntries(ids: [entryId], status: "read") try? await api.markEntries(ids: [entryId], status: "read")
vm.counters = try? await api.getCounters() vm.counters = try? await api.getCounters()
} }
// 3. Fetch full content update when ready let t1 = CFAbsoluteTimeGetCurrent()
do { do {
let fullEntry = try await ReaderAPI().getEntry(id: entryId) let fullEntry = try await ReaderAPI().getEntry(id: entryId)
let fullHTML = fullEntry.articleHTML let t2 = CFAbsoluteTimeGetCurrent()
print("[ART-OPEN] getEntry returned +\(Int((t2-t0)*1000))ms contentLen=\(fullEntry.articleHTML.count)")
let fullHTML = fullEntry.articleHTML
if !fullHTML.isEmpty && fullHTML.count > articleContent.count { if !fullHTML.isEmpty && fullHTML.count > articleContent.count {
articleContent = fullHTML articleContent = fullHTML
print("[ART-OPEN] articleContent updated +\(Int((CFAbsoluteTimeGetCurrent()-t0)*1000))ms")
} else {
print("[ART-OPEN] content NOT upgraded (existing=\(articleContent.count) new=\(fullHTML.count))")
} }
// Preserve local status/starred (may differ from server due to race)
var merged = fullEntry var merged = fullEntry
if let local = vm.entries.first(where: { $0.id == entryId }) { if let local = vm.entries.first(where: { $0.id == entryId }) {
merged.status = local.status merged.status = local.status
@@ -93,8 +98,9 @@ struct ArticleView: View {
} }
currentEntry = merged currentEntry = merged
} catch { } catch {
// Keep whatever content we already have print("[ART-OPEN] getEntry FAILED: \(error)")
} }
print("[ART-OPEN] .task complete +\(Int((CFAbsoluteTimeGetCurrent()-t0)*1000))ms")
} }
} }

View File

@@ -49,6 +49,7 @@ struct ArticleWebView: UIViewRepresentable {
let html: String let html: String
func makeUIView(context: Context) -> UIView { func makeUIView(context: Context) -> UIView {
print("[ART-WV] makeUIView")
let container = UIView() let container = UIView()
container.backgroundColor = .clear container.backgroundColor = .clear
@@ -74,6 +75,8 @@ struct ArticleWebView: UIViewRepresentable {
let isUpgrade = context.coordinator.lastHTML != nil let isUpgrade = context.coordinator.lastHTML != nil
context.coordinator.lastHTML = newHTML context.coordinator.lastHTML = newHTML
print("[ART-WV] updateUIView isUpgrade=\(isUpgrade) htmlLen=\(newHTML.count)")
if isUpgrade { if isUpgrade {
// Content upgrade (partial full): swap only #article-body contents. // Content upgrade (partial full): swap only #article-body contents.
// Header + outer document structure stay intact. Scroll preserved. // Header + outer document structure stay intact. Scroll preserved.
@@ -135,6 +138,7 @@ struct ArticleWebView: UIViewRepresentable {
class Coordinator: NSObject, WKNavigationDelegate { class Coordinator: NSObject, WKNavigationDelegate {
var lastHTML: String? var lastHTML: String?
var loadStart: CFAbsoluteTime = 0
func webView( func webView(
_ webView: WKWebView, _ webView: WKWebView,
@@ -149,5 +153,15 @@ struct ArticleWebView: UIViewRepresentable {
} }
decisionHandler(.allow) decisionHandler(.allow)
} }
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
loadStart = CFAbsoluteTimeGetCurrent()
print("[ART-WV] didStartNavigation")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let elapsed = Int((CFAbsoluteTimeGetCurrent() - loadStart) * 1000)
print("[ART-WV] didFinish +\(elapsed)ms")
}
} }
} }