fix(library): keep the wall row step, so its window cannot chase its own tail
Selecting thumbs or flipping SINGLE <-> ALL THUMBS on a roll whose captions wrap to different heights crashed the shelf with React #185 ("Maximum update depth exceeded") and left the screen blank. useWallWindow read the step from one row to the next off the rows the window itself had drawn, and the average of a run of rows moves as the run grows: `to` traded places with a sum a row away — 35, 40, 35, 40, three milliseconds apart — every pass a render, until React gave up. The step is taken once per column count and kept, re-read when the wall re-columns, which is the only thing that changes a card own height (a caption wraps at the width it is given). The window is a function of the scroll and nothing else, so it settles. Reproduced with the storm probe against a 3000-frame roll of unevenly named frames: before, blank at round 1 with #185; after, 12 rounds of hard SINGLE/ALL THUMBS toggling plus a round trip through the studio — no error, no blank screen, and D18 unchanged (tile CHANGED, the shelf raising the studio frame 4px off centre, the studio link opening the same frame).
This commit is contained in:
@@ -3070,6 +3070,12 @@ function useWallWindow(count: number, on: boolean): { ref: RefObject<HTMLDivElem
|
||||
// stands at.
|
||||
const winRef = useRef(win);
|
||||
const measureRef = useRef<() => void>(() => {});
|
||||
// The step from one row to the next, taken once per column count and kept. It
|
||||
// is a ref rather than a value read on every pass because the pass reads it off
|
||||
// the rows the window drew — and a step that moves with the window is a window
|
||||
// that moves with the step: the two trade places forever (see `measure`).
|
||||
const pitchRef = useRef(0);
|
||||
const colsRef = useRef(0);
|
||||
// The window over a scroll container is taken rather than kept, so it is taken
|
||||
// after every render: the first pass draws a screenful, the next reads the row's
|
||||
// step off it and lays out the spacers, and the pass after that leaves both
|
||||
@@ -3108,7 +3114,23 @@ function useWallWindow(count: number, on: boolean): { ref: RefObject<HTMLDivElem
|
||||
// not in hand.
|
||||
const rowsDrawn = Math.floor(cards.length / cols);
|
||||
if (rowsDrawn < 2) return;
|
||||
const pitch = (cards[(rowsDrawn - 1) * cols].getBoundingClientRect().top - cards[0].getBoundingClientRect().top) / (rowsDrawn - 1);
|
||||
// The step is read off the drawn rows the first time the wall stands at this
|
||||
// many columns, and kept until it stands at another: the average of a run of
|
||||
// rows moves as the run grows, so a step re-read on every pass made the window
|
||||
// depend on itself — `to` traded places with a second sum a row away, sixty
|
||||
// times a second, until React gave up with #185 and the screen went blank. One
|
||||
// step, one window. It is re-read when the wall re-columns, the only thing that
|
||||
// changes a card's own height (a caption wraps at the width it is given); a
|
||||
// resize that does not re-column keeps the step, and a spacer a few pixels out
|
||||
// is a scrollbar a few pixels out, not a blank screen.
|
||||
if (colsRef.current !== cols) {
|
||||
const drawn = (cards[(rowsDrawn - 1) * cols].getBoundingClientRect().top - cards[0].getBoundingClientRect().top) / (rowsDrawn - 1);
|
||||
if (drawn > 0) {
|
||||
colsRef.current = cols;
|
||||
pitchRef.current = drawn;
|
||||
}
|
||||
}
|
||||
const pitch = pitchRef.current;
|
||||
if (!(pitch > 0)) return;
|
||||
const rows = Math.ceil(count / cols);
|
||||
const row0 = Math.max(0, Math.floor(el.scrollTop / pitch) - WALL_ROWS);
|
||||
|
||||
Reference in New Issue
Block a user