Files
siftlode/frontend/src/components/ui/progressiveRows.ts
T
peter 4aaaaa7860 chore(comments): drop epic, sprint and review-round ids from the code
The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").

Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.

Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).

One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
2026-10-05 00:19:39 +02:00

39 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { startTransition, useEffect, useState } from "react";
// A long page of table rows mounts in steps: the first FIRST_ROWS at once, then STEP_ROWS more per
// background transition until all are in. The channel manager's "All" page (~400 rows) took
// 0.7–0.9 s of one blocked frame to mount on prod, and every sort and every keystroke in its search
// re-rendered all of them (300–400 ms per key). No single cell is to blame: measured column by column,
// each costs its share, and all of them together cost ~9× an empty row. The first rows fill more than
// a screen, so the page paints at once. The rest come in small steps because a transition yields only
// while it renders: its commit (the rows' DOM, the tag cells' measuring layout effects) is one block,
// and the rest in one step was a 0.7 s block of its own. Between steps a key or a click goes first,
// and a new view starts over.
//
// Not virtualization: every row still ends up in the DOM, so browser find, keyboard nav past the screen,
// and the Back focus/scroll restore (which wait for the list to grow) keep working as they did.
export const FIRST_ROWS = 30;
const STEP_ROWS = 40;
// `view` names what the rows show (page, size, sort, filters). A new view starts over from the first
// rows, so the urgent render after a click or a key stays small. New data for the same view (a refetch)
// does not: it would drop the rows the reader is looking at.
//
// `through` is a row that must be in the DOM in THIS render: the row a page turn was made to show
// (DataTable's `reveal`). Its page is a new view, and the caller focuses that row right after the
// commit — at index 40 of a 50-row page it was not mounted yet, and focus fell to <body>.
export function useProgressiveRows<T>(rows: T[], view: string, through = -1): T[] {
const [seen, setSeen] = useState(view);
const [limit, setLimit] = useState(FIRST_ROWS);
if (seen !== view) {
setSeen(view);
setLimit(FIRST_ROWS);
}
const count = Math.max(limit, through + 1);
const more = rows.length > count;
useEffect(() => {
if (more) startTransition(() => setLimit(count + STEP_ROWS));
}, [more, count]);
return more ? rows.slice(0, count) : rows;
}