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
_currentEntry = State(initialValue: entry)
_articleContent = State(initialValue: entry.articleHTML)
print("[ART-OPEN] init contentLen=\(entry.articleHTML.count)")
}
var body: some View {
@@ -59,33 +60,37 @@ struct ArticleView: View {
}
}
.task {
// 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 t0 = CFAbsoluteTimeGetCurrent()
print("[ART-OPEN] .task started")
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
print("[ART-OPEN] mark-read done +\(Int((CFAbsoluteTimeGetCurrent()-t0)*1000))ms")
// 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
let t1 = CFAbsoluteTimeGetCurrent()
do {
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 {
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
if let local = vm.entries.first(where: { $0.id == entryId }) {
merged.status = local.status
@@ -93,8 +98,9 @@ struct ArticleView: View {
}
currentEntry = merged
} 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
func makeUIView(context: Context) -> UIView {
print("[ART-WV] makeUIView")
let container = UIView()
container.backgroundColor = .clear
@@ -74,6 +75,8 @@ struct ArticleWebView: UIViewRepresentable {
let isUpgrade = context.coordinator.lastHTML != nil
context.coordinator.lastHTML = newHTML
print("[ART-WV] updateUIView isUpgrade=\(isUpgrade) htmlLen=\(newHTML.count)")
if isUpgrade {
// Content upgrade (partial full): swap only #article-body contents.
// Header + outer document structure stay intact. Scroll preserved.
@@ -135,6 +138,7 @@ struct ArticleWebView: UIViewRepresentable {
class Coordinator: NSObject, WKNavigationDelegate {
var lastHTML: String?
var loadStart: CFAbsoluteTime = 0
func webView(
_ webView: WKWebView,
@@ -149,5 +153,15 @@ struct ArticleWebView: UIViewRepresentable {
}
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")
}
}
}