debug: add scroll mark-as-read instrumentation for first 3 entries
Temporary logging to identify which guard condition prevents marking. Logs visibility ratio, scroll state, and failure reasons for entries that are above viewport but not being marked. Will remove after fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -111,15 +111,13 @@ struct EntryListView: View {
|
|||||||
// MARK: - Entry Row with Scroll-Read Tracking
|
// MARK: - Entry Row with Scroll-Read Tracking
|
||||||
|
|
||||||
private func scrollTracked(_ entry: ReaderEntry, content: some View) -> some View {
|
private func scrollTracked(_ entry: ReaderEntry, content: some View) -> some View {
|
||||||
content
|
let debugThis = vm.entries.prefix(3).contains(where: { $0.id == entry.id })
|
||||||
|
return content
|
||||||
.background(
|
.background(
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
let frame = geo.frame(in: .named("readerScroll"))
|
let frame = geo.frame(in: .named("readerScroll"))
|
||||||
Color.clear
|
Color.clear
|
||||||
.onChange(of: frame.minY) { _, _ in
|
.onChange(of: frame.minY) { _, _ in
|
||||||
// Track visibility: if >=50% of the entry is within the viewport,
|
|
||||||
// record it as "was visible". Only entries that were genuinely seen
|
|
||||||
// can later be marked as read on scroll-past.
|
|
||||||
let entryHeight = frame.height
|
let entryHeight = frame.height
|
||||||
guard entryHeight > 0 else { return }
|
guard entryHeight > 0 else { return }
|
||||||
let visibleTop = max(frame.minY, 0)
|
let visibleTop = max(frame.minY, 0)
|
||||||
@@ -130,27 +128,36 @@ struct EntryListView: View {
|
|||||||
if visibleRatio >= 0.5 {
|
if visibleRatio >= 0.5 {
|
||||||
wasVisible.insert(entry.id)
|
wasVisible.insert(entry.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if debugThis {
|
||||||
|
print("[SCROLL-VIS] id=\(entry.id) minY=\(Int(frame.minY)) maxY=\(Int(frame.maxY)) height=\(Int(entryHeight)) visH=\(Int(visibleHeight)) ratio=\(String(format:"%.2f", visibleRatio)) vpH=\(Int(viewportHeight)) wasVis=\(wasVisible.contains(entry.id))")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: frame.maxY) { _, newMaxY in
|
.onChange(of: frame.maxY) { _, newMaxY in
|
||||||
// Mark as read when entry is above viewport.
|
if debugThis {
|
||||||
// 5 conditions must be true:
|
print("[SCROLL-MRK] id=\(entry.id) maxY=\(Int(newMaxY)) active=\(trackingActive) down=\(isScrollingDown) read=\(entry.isRead) marked=\(markedByScroll.contains(entry.id)) wasVis=\(wasVisible.contains(entry.id)) cumDown=\(Int(cumulativeDown)) thresh=\(Int(activationThreshold))")
|
||||||
// 1. Tracking active (scrolled past dynamic threshold)
|
}
|
||||||
// 2. Scrolling downward
|
|
||||||
// 3. Entry is unread
|
|
||||||
// 4. Not already marked by scroll
|
|
||||||
// 5. Was >=50% visible at some point (genuinely seen)
|
|
||||||
//
|
|
||||||
// We check newMaxY < 0 (entry fully above viewport)
|
|
||||||
// instead of requiring an exact crossing event, because
|
|
||||||
// LazyVStack can recycle the view between onChange calls,
|
|
||||||
// causing the 0-boundary crossing to never be delivered.
|
|
||||||
guard trackingActive,
|
guard trackingActive,
|
||||||
isScrollingDown,
|
isScrollingDown,
|
||||||
!entry.isRead,
|
!entry.isRead,
|
||||||
!markedByScroll.contains(entry.id),
|
!markedByScroll.contains(entry.id),
|
||||||
wasVisible.contains(entry.id),
|
wasVisible.contains(entry.id),
|
||||||
newMaxY < 0 else { return }
|
newMaxY < 0 else {
|
||||||
|
if debugThis && newMaxY < 0 {
|
||||||
|
// Log WHY it failed when entry IS above viewport
|
||||||
|
var reasons: [String] = []
|
||||||
|
if !trackingActive { reasons.append("tracking-inactive") }
|
||||||
|
if !isScrollingDown { reasons.append("not-scrolling-down") }
|
||||||
|
if entry.isRead { reasons.append("already-read") }
|
||||||
|
if markedByScroll.contains(entry.id) { reasons.append("already-marked") }
|
||||||
|
if !wasVisible.contains(entry.id) { reasons.append("never-visible") }
|
||||||
|
print("[SCROLL-FAIL] id=\(entry.id) maxY=\(Int(newMaxY)) reasons=\(reasons.joined(separator: ","))")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("[SCROLL-READ] ✅ id=\(entry.id) MARKED AS READ")
|
||||||
markedByScroll.insert(entry.id)
|
markedByScroll.insert(entry.id)
|
||||||
|
|
||||||
// Local-first: update immediately
|
// Local-first: update immediately
|
||||||
|
|||||||
Reference in New Issue
Block a user