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.
39 lines
2.2 KiB
TypeScript
39 lines
2.2 KiB
TypeScript
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;
|
||
}
|