Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

marko-virtual: scrollToEnd during an in-flight prepend strands the view one prepend above the bottom (chat-pretext e2e fails on CI)

Aperta
#1,267 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
55/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
playwright, typescript
Ambito
frontend, testing

Direzione di ricerca

Inizia con il flusso onUpdate del tag virtualizer e l'adapter Marko in packages/marko-virtual, poi esamina examples/marko/chat-pretext/src/routes/+page.marko e il test e2e/chat-pretext.spec.ts che fallisce. Confronta l'ordine con la modifica applyContainerSize di react-virtual in #1237 ed esegui la riproduzione deterministica di latest-race. Il lavoro è completato quando un prepend tardivo dopo scrollToEnd mantiene la vista in fondo e il test e2e passa senza fixme.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Describe the bug

In the Marko chat-pretext example (and the e2e test built on it), calling scrollToEnd() while a history prepend is in flight leaves the view stranded exactly one prepend above the bottom, with no scroll event to recover it. The e2e/chat-pretext.spec.ts › Latest returns to the bottom and status flips back to At latest test fails deterministically on CI because of this (see #1260, two consecutive runs), while passing locally where the timing differs.

Root cause

The <virtualizer> tag's onUpdate runs setOptions(...), then _willUpdate(), then notify():

current.setOptions({ ...current.options, ...buildOptions(input, notify) })
current._willUpdate()
notify()   // ← sets the reactive `size`; the sizer DOM grows in a later batch

On an end-anchored prepend, setOptions bumps the tracked scrollOffset by the prepended height and _willUpdate writes that offset to scrollTop. At that moment the sizer still has the old height, so the browser clamps the write to the old maximum. The sizer grows afterwards, scrollTop never moves, and the DOM is left prependedHeight short of the end. Core's tracked offset already holds the new value, so isAtEnd() reports true and the example's pinPending re-issue (which exists for exactly this strand, see the comment in examples/marko/chat-pretext/src/routes/+page.marko) disarms without correcting anything.

This is the same ordering problem react-virtual fixed in #1237 by growing the container before _willUpdate syncs the scroll position.

Reproduction (deterministic)

Drop this into packages/marko-virtual/e2e/app/e2e/ and run CI=1 npx playwright test latest-race. Fails 4/4 on main (and on #1260) with distance 870 (12 rows × ~72px):

import { expect, test } from '@playwright/test'
import type { Page } from '@playwright/test'
const dist = (page: Page) =>
  page.locator('.messages').evaluate((el) => el.scrollHeight - el.scrollTop - el.clientHeight)

// Same as "Latest returns to the bottom" but clicks Latest right after arming the
// auto history load, so the 180ms prepend lands AFTER the jump — the ordering CI hits.
for (let i = 0; i < 4; i++) {
  test(`race ${i}: Latest then late prepend keeps the bottom`, async ({ page }) => {
    await page.goto('/chat-pretext')
    await expect.poll(() => dist(page), { timeout: 5000 }).toBeLessThanOrEqual(80)
    await page.waitForTimeout(400) // autoHistoryEnabled arms after 250ms
    await page.locator('.messages').evaluate((el) => { el.scrollTop = 0 })
    await page.waitForTimeout(30)
    await page.locator('[data-testid="latest"]').click()
    await page.waitForTimeout(1500)
    expect(await dist(page)).toBeLessThanOrEqual(80)
  })
}
Expected behavior

scrollToEnd() followed by a late prepend keeps the viewport pinned to the bottom (or, at minimum, the prepend keeps the visible content anchored, which for a bottom-pinned view means staying at the bottom).

Possible fixes
  1. Marko adapter: write the sizer height to the DOM synchronously before calling _willUpdate(), mirroring react-virtual's applyContainerSize ordering from #1237.
  2. Core: extend the clamp-detect-and-retry introduced in #1260 for compensation writes to the anchor-sync write in _willUpdate, so every adapter recovers regardless of DOM update ordering.

Until one of those lands, the affected e2e test is marked test.fixme referencing this issue so unrelated PRs can go green.

Platform

Chromium (Playwright), Linux CI runner; reproducible on macOS with the forced-timing spec above.

tanstack-virtual version

@tanstack/virtual-core@3.17.8, @tanstack/marko-virtual current main.

Lingua principale
TypeScript
Stelle
7.1k
Fork
466
Merge medio
2g 31m
PR unite (30g)
13

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di TanStack/virtual

Tutte le issue di TanStack/virtual

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.