debug: timestamped logging for first-article-open stall investigation
This commit is contained in:
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user