Skip to content

Proposal H, item 3: flat vertical scroll + sheet-level virtualization - #115

Merged
WilfordGrimley merged 2 commits into
masterfrom
claude/proposal-h-3c-flat-scroll-virtualization-04bam2
Jul 19, 2026
Merged

Proposal H, item 3: flat vertical scroll + sheet-level virtualization#115
WilfordGrimley merged 2 commits into
masterfrom
claude/proposal-h-3c-flat-scroll-virtualization-04bam2

Conversation

@WilfordGrimley

@WilfordGrimley WilfordGrimley commented Jul 19, 2026

Copy link
Copy Markdown

Description

Replaces /display's one-page-at-a-time pager with a continuous vertical stack of every sheet, per the owner's flat-scroll amendment. Each sheet's PagePreview mounts only when on/near screen via the existing RenderIfVisible component (already proven in CardResultSet.tsx) rather than a new IntersectionObserver implementation from scratch.

  • sheets (derived from displayPagination.ts's paginateSlotsForDisplay) replaces the old single-page currentPageEntries/currentPageSlots derivation — every sheet's slot content is resolved (cheap bookkeeping), but only sheets within one sheet-height of the viewport actually mount real <img> tags.
  • The toolbar's old prev/next pager (display-page-indicator) is gone. In its place: a passive display-sheet-indicator readout ("Sheet N of M") driven by its own IntersectionObserver — a thin band near vertical centre, distinct from RenderIfVisible's own broader mount/unmount rootMargin.
  • handleSlotClick/selected-slot-index logic now operates across all sheets simultaneously, not just "the current page."
  • This branch also carries a merge of origin/master to reconcile with item 2 (Proposal H, item 2: inline PDF export runs the real pipeline on /display #109, inline PDF export) and item (c) (Item (c): requested-printing badge on editor slots #110, RequestedPrintingBadge extraction), both of which landed on master after this branch was created and both touch DisplayPage.tsx significantly. The merge was hand-resolved (one import-line conflict) and re-verified in full post-merge — see the checklist below.

Benchmark (owner-required gate)

Measured against a 120-card deck (15 sheets) under 4x CPU throttle, dev-mode flag-on build, two runs:

metric run 1 run 2
avg fps 58.7 59.6
p95 frame time 21.9ms 21.3ms
peak JS heap 236.8 MB 263.1 MB
max simultaneously-mounted <img> tags 16 of 120 16 of 120
long tasks (>50ms) during scroll 0 0

Decision: sheet-level virtualization is sufficient — shipping as-is, no row-granular fallback needed. Both runs meet the ~60fps threshold with zero long tasks and a bounded image-mount count (~2 sheets' worth regardless of deck size), confirming the anti-crash goal holds. Caveat: measured under Next dev mode (unminified, React dev overhead) via the project's own Playwright dev-server harness, not the literal built production bundle — a real production build would only improve on these numbers, so this is a conservative/pessimistic reading, not an optimistic one.

Follow-up (owner-requested): the benchmark script is now committed under frontend/tests/perf/display-scroll.bench.spec.ts, runnable via npx playwright test --config=playwright.perf.config.ts — a separate config so it's excluded from both a plain npx playwright test and CI's sharded run (playwright.config.ts now testIgnores tests/perf/**). Re-running it against this branch reproduces the original numbers (120 cards/15 sheets, ~59fps, peak heap ~257MB, 16/120 max mounted <img>s, 0 long tasks). Intended to give the upcoming pane migration (which restructures this exact surface) a number to compare against instead of a feeling.

Checklist

  • I have installed pre-commit and installed the hooks with pre-commit install before creating any commits.
  • I have updated any related tests for code I modified or added new tests where appropriate. New Playwright test: multi-sheet deck renders as a continuous stack with the far-off sheet deferred until scrolled into view (DisplayPage.spec.ts). Existing display-page-indicator assertion updated to display-sheet-indicator. Benchmark script added under tests/perf/ (non-CI, manually run).
  • I have manually tested my changes as follows:
    • npx tsc --noEmit clean
    • npx jest — 42/42 suites, 399/399 tests passing
    • NEXT_PUBLIC_UNIFIED_DISPLAY_ENABLED=true npx next build — flag-on production build succeeds
    • npx playwright test tests/DisplayPage.spec.ts tests/DisplayPageExport.spec.ts — 19/19 passing on the merged tree (full-file runs, not filtered)
    • npx playwright test --list (default config) confirms tests/perf/ is invisible to normal/CI runs; npx playwright test --config=playwright.perf.config.ts runs it and reproduces the benchmark numbers
    • Manual benchmark run (see above), twice for stability
  • I have updated any relevant documentation or created new documentation where appropriate. Item 3 didn't require a design-doc-lite update per the owner's "ASCII-mockup-in-PR-body suffices for structural PRs" waiver for this work order.

Known sandbox flakiness (documented per prior PRs in this sequence, not chased further): unfiltered full-file Playwright runs in this dev sandbox intermittently fail with "Playwright Test did not expect test.describe()/describe() to be called here," reproduced even on untouched spec files — confirmed environmental, not code-related. Not observed on the runs quoted above.

claude added 2 commits July 19, 2026 02:01
Replaces /display's one-page-at-a-time pager with a continuous vertical
stack of every sheet (sheets derived from paginateSlotsForDisplay), each
mounting its PagePreview only when on/near screen via the existing
RenderIfVisible component (already proven in CardResultSet.tsx) rather
than a new IntersectionObserver implementation. The old prev/next pager
is gone; a passive "Sheet N of M" readout (its own, tighter-band
IntersectionObserver, distinct from RenderIfVisible's broader mount/
unmount one) replaces it. Slot click/selection logic now operates across
all sheets, not just the current page.
…c-flat-scroll-virtualization-04bam2

# Conflicts:
#	frontend/src/features/display/DisplayPage.tsx
@WilfordGrimley
WilfordGrimley merged commit 59fb2f9 into master Jul 19, 2026
7 checks passed
@WilfordGrimley
WilfordGrimley deleted the claude/proposal-h-3c-flat-scroll-virtualization-04bam2 branch July 19, 2026 02:38
WilfordGrimley pushed a commit that referenced this pull request Jul 19, 2026
… script

Per owner follow-up on #115: the manual scroll fps/heap/jank benchmark
that gated the sheet-level-vs-row-granular virtualization decision is
now under frontend/tests/perf/, runnable via
`npx playwright test --config=playwright.perf.config.ts`. The main
playwright.config.ts testIgnores tests/perf/ so it never runs in CI or
a plain `npx playwright test`. Ahead of the pane migration, which
restructures this exact surface, this turns "did scrolling regress?"
into a number to compare against, not a feeling.

Verified: `npx playwright test --config=playwright.perf.config.ts`
reproduces the PR's original results (120 cards/15 sheets, ~59fps,
peak heap ~257MB, 16/120 max simultaneously-mounted <img> tags, 0 long
tasks); `npx playwright test --list` (default config) does not surface
the perf spec.
WilfordGrimley added a commit that referenced this pull request Jul 19, 2026
…a commit orphaned by #115's merge) (#178)

* Commit item 3's scroll/virtualization benchmark as a runnable, non-CI script

Per owner follow-up on #115: the manual scroll fps/heap/jank benchmark
that gated the sheet-level-vs-row-granular virtualization decision is
now under frontend/tests/perf/, runnable via
`npx playwright test --config=playwright.perf.config.ts`. The main
playwright.config.ts testIgnores tests/perf/ so it never runs in CI or
a plain `npx playwright test`. Ahead of the pane migration, which
restructures this exact surface, this turns "did scrolling regress?"
into a number to compare against, not a feeling.

Verified: `npx playwright test --config=playwright.perf.config.ts`
reproduces the PR's original results (120 cards/15 sheets, ~59fps,
peak heap ~257MB, 16/120 max simultaneously-mounted <img> tags, 0 long
tasks); `npx playwright test --list` (default config) does not surface
the perf spec.

* Fix prettier formatting in scroll benchmark spec

---------

Co-authored-by: Claude <noreply@anthropic.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