Skip to content

fix(zero-swift): stop paging flips after a short permalink list's row goes and past short rows - #84

Merged
tmm merged 1 commit into
mainfrom
swift-virtual-paging-flips
Nov 3, 2026
Merged

tmm merged 1 commit into
mainfrom
swift-virtual-paging-flips

Conversation

@tmm

@tmm tmm commented Nov 3, 2026

Copy link
Copy Markdown
Member

Follow-up to #80 and #81: the rare paging loop #81's randomized run found, plus a regression from #80 that the same run turned up. In both, the loaded window flips between two pages without settling.

1. A short permalinked list whose row is deleted pages forever (regression in 0.6.2). A list opened at permalinkID, short enough that all its rows fit in one page and scrolled to its end, never settles after its permalink row is deleted. It alternates between r1…r14 and r0…r13 on every pass, running a page query on each one. 0.6.1 sent the list to the top and settled.

2. Scrolled past the loaded rows of short rows, the next page comes in below the viewport (the loop from #81, seed 209). When rows are shorter than estimatedRowHeight and the viewport goes past the loaded rows into estimated space, there's a blank frame and the list pages back to its start. In the worst case it flips between the two pages forever.

  • Cause: fix(zero-swift): line up clamped jumps, re-pick the anchor row, and page onto the screen above a permalink #81's branch for a page that shares no row with the last one moves the space by a row estimate per index. That's right paging back, where the rows passed are the new page's and aren't measured yet. Paging forward, they're the last page's, already laid out: at 12pt rows against a 20pt estimate, the next page landed 40% further down than the rows end.
  • Fix: paging forward, the space moves by the last page's laid-out height, plus an estimate for any rows between the two pages.

Tests. Both new LayoutHostTests fail on main, and each one fails without its own half of the fix:

  • "deleting the row a list shorter than a page opened at, the list settles on all its rows"
  • "scrolled past the loaded rows of rows shorter than estimated, the next page is on screen"

Verified locally (Linux): all 114 ZeroVirtualTests pass, and swift format lint is clean. Over 1000 seeds of the bug hunt's randomized run, with rows kept between 10 and 200pt, failures went from 10 to 8. Seed 209 and the two short-list loops are fixed, and nothing new fails.

Not fixed here: the other 8 failures. 7 are lists stuck to the bottom whose end window is short. A backward page from the window's last row leaves that row out, the space estimated after it moves the bottom, the re-pin follows it, and the list pages forward again. Upstream zero-virtual f89c468 has the same edge paging. Fixing it needs a backward page that ends at the list's end, which VirtualAnchor.backward can't represent because its startRow isn't optional. The 8th is a jump where a row moves (seed 760).

Amp-Thread-ID: https://ampcode.com/threads/T-c513a037-a257-45e0-b9a9-15c15edfe859

… goes and past short rows

Two ways the loaded window could flip between two pages, neither putting
the right rows on screen, both found by a seeded randomized run over
LayoutHost:

- A forward page from the first row of a window already at the list's
  start leaves that row out. After a short list's permalink row was
  deleted (#80), it paged forward from its first row, losing r0, then
  back from its last, losing the end, forever. That page now comes from
  the list's start.
- A page that shares no row with the last one went where the rows'
  estimate put it. Paging forward, the rows passed are the last page's,
  already laid out: with rows shorter than their estimate, the next page
  came in below the viewport, and the list paged back. It now comes in
  where they end.

Amp-Thread-ID: https://ampcode.com/threads/T-c513a037-a257-45e0-b9a9-15c15edfe859
Co-authored-by: tmm <tmm@tmm.dev>
@tmm
tmm merged commit 65ea14e into main Nov 3, 2026
25 checks passed
@tmm
tmm deleted the swift-virtual-paging-flips branch November 3, 2026 23:33
tmm added a commit that referenced this pull request Nov 3, 2026
…ttom settles there

Stuck to the bottom, with the window at the end down to a few rows (as
after messages near the end are deleted and the user scrolls back down),
the list loaded pages without settling. The page before the window's
last row leaves that row out; the space estimated in its place moved the
bottom, the list followed it, paged forward to the end again, and so on.

VirtualAnchor.backward's startRow is now optional: nil is the page that
ends at the list's end, which a page query gets as a backward request
with no start, as VirtualPageRequest already documented. A backward page
from the last row of a window at the list's end is that page.
Zero-virtual's backward anchor always has a start row; UPSTREAM.md
records the divergence, and #84's two.

Amp-Thread-ID: https://ampcode.com/threads/T-c513a037-a257-45e0-b9a9-15c15edfe859
Co-authored-by: tmm <tmm@tmm.dev>
tmm added a commit that referenced this pull request Nov 4, 2026
…ttom settles there (#85)

Stuck to the bottom, with the window at the end down to a few rows (as
after messages near the end are deleted and the user scrolls back down),
the list loaded pages without settling. The page before the window's
last row leaves that row out; the space estimated in its place moved the
bottom, the list followed it, paged forward to the end again, and so on.

VirtualAnchor.backward's startRow is now optional: nil is the page that
ends at the list's end, which a page query gets as a backward request
with no start, as VirtualPageRequest already documented. A backward page
from the last row of a window at the list's end is that page.
Zero-virtual's backward anchor always has a start row; UPSTREAM.md
records the divergence, and #84's two.

Amp-Thread-ID: https://ampcode.com/threads/T-c513a037-a257-45e0-b9a9-15c15edfe859

Co-authored-by: Amp <amp@ampcode.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants