E32 S4 — the burn-down from 29 skipped functions to 1: - try/finally (26 sites): the cleanup runs after a try/catch; steps that return early moved into their own function (push, the setup wizard's next, the share page's unlock). - A conditional, loop or optional call inside a try skips the function too, and react-hooks/todo does not see it: the value is computed before the try, or the try lives in a module-level helper (config writes, clip queueing, clipboard, frame counts, seekable end, YouTube player times). - The two exhaustive-deps suppressions are gone. useYtProgress takes its per-item snapshot through one effect event, read once when the player is built; PlexPlayer's unmount relies on the leaving-the-item cleanup for the generation bump it duplicated. - What the skips hid: PlexPlayer's 17 render-phase ref writes (useLatest), its tracks popover alias, the loadSession self-reference (declared early, set after commit); PlayerModal's session-position read during render (now where the player starts), navRef/modeRef writes, and a description helper that ran a ref-carrying callback during render (now PlayerDescription); SavedViewsProvider's latest-cfg ref, and its setState in the seed effect (preferSticky now prefers the starred default among same-filter views, so the seed needs no sticky write — test seen red on the old helper). - A store's hook was called `use()`, which the compiler does not know as a hook: inside the newly compiled usePlaylistActions it was memoized, ran on the first render alone, and the playlist rail threw "rendered fewer hooks" on its second render. Renamed to `useValue()` everywhere; the old contract test claimed the call was safe inside any `use…` function, which was false — replaced by a render test that is red under the old name. - PlexPlayer's palette ref was written and never read since the Backspace guard moved to the layer stack: removed. react-hooks/refs is back at error with no exception. The ratchet lists only VirtualGrid (IncompatibleLibrary) and pins both players as compiled. No size or complexity ceiling was raised.
71 lines
3.8 KiB
TypeScript
71 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { compileReport, shippedSources, type CompileReport } from "./compilerSkips.testkit";
|
|
|
|
// The React Compiler bails out of a component it cannot prove safe WITHOUT a word: the component
|
|
// still works, it just re-renders everything under it on every render. The channel manager is where
|
|
// it hurt (the owner, 2026-10-03): Channels and DataTable both bailed out, every open, close and
|
|
// reposition of a row's right-click menu rebuilt the rows and columns, and on "All" rows the menu
|
|
// took ~0.9 s to open and ~0.6 s to close. The E32 audit then found 95 such functions across src.
|
|
//
|
|
// A RATCHET over everything the build compiles (E32 S1). KNOWN_SKIPS is the exact set the compiler
|
|
// skips today, `file → function → the compiler's category`; everything else must compile. Both
|
|
// directions are red:
|
|
// • a NEW skip (or a known one changing category) — fix the function; if it truly cannot be
|
|
// compiled, adding it here is a decision for the owner, with the reason in docs/gates.md;
|
|
// • a skip that is GONE — delete its entry, so the list only ever shrinks.
|
|
// E32 burned it down from 93 to the one function below: S3 read the popover engine through
|
|
// destructuring and moved every render-phase ref write to `useEffectEvent` / `useLatest`; S4 took
|
|
// try/finally and every conditional, loop or optional call out of try blocks (the compiler cannot
|
|
// lower them yet — and `react-hooks/todo` does not see the latter, so THIS test is what catches them)
|
|
// and replaced the two exhaustive-deps suppressions, each of which skipped its whole function.
|
|
// VirtualGrid stays: TanStack Virtual's API cannot be memoized safely (IncompatibleLibrary).
|
|
const KNOWN_SKIPS: CompileReport["skipped"] = {
|
|
"components/ui/VirtualGrid.tsx": { VirtualGrid: "IncompatibleLibrary" },
|
|
};
|
|
|
|
// The files a regression hurt most: compiled, and they must stay so (a positive check too, so a
|
|
// harness that compiled nothing could not pass as "no skips").
|
|
const MUST_COMPILE: Record<string, string[]> = {
|
|
"components/Channels.tsx": ["Channels"],
|
|
"components/ui/DataTable.tsx": ["DataTable"],
|
|
// Under every button and every popover in the app (E32 S3).
|
|
"components/ui/FocusSafeButton.tsx": ["FocusSafeButton"],
|
|
"components/ui/anchoredPopoverHooks.ts": ["useAnchoredPopover"],
|
|
"components/ui/AnchoredPopover.tsx": ["PopoverSurfaceInner"],
|
|
// The two players — the largest components here, and the last to compile (E32 S4).
|
|
"components/PlexPlayer.tsx": ["PlexPlayer"],
|
|
"components/PlayerModal.tsx": ["PlayerModal"],
|
|
"lib/playerModalHooks.ts": ["useYtProgress"],
|
|
};
|
|
|
|
type Row = [file: string, fn: string, category: string];
|
|
const rows = (skips: CompileReport["skipped"]): Row[] =>
|
|
Object.entries(skips).flatMap(([file, fns]) =>
|
|
Object.entries(fns).map(([fn, category]): Row => [file, fn, category]),
|
|
);
|
|
|
|
describe("the React Compiler", () => {
|
|
// ~8 s for the whole tree, once, shared by the checks below.
|
|
const report = compileReport(shippedSources());
|
|
const actual = rows(report.skipped);
|
|
const known = rows(KNOWN_SKIPS);
|
|
const key = ([f, fn]: Row) => `${f} → ${fn}`;
|
|
|
|
it("skips nothing new (fix it, or the owner decides to list it)", () => {
|
|
const knownAs = new Map(known.map((r) => [key(r), r[2]]));
|
|
expect(
|
|
actual.filter((r) => knownAs.get(key(r)) !== r[2]).map((r) => `${key(r)}: ${r[2]}`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("lists no skip that has gone (delete it — the list only shrinks)", () => {
|
|
const actualAs = new Map(actual.map((r) => [key(r), r[2]]));
|
|
expect(known.filter((r) => !actualAs.has(key(r))).map(key)).toEqual([]);
|
|
});
|
|
|
|
it.each(Object.entries(MUST_COMPILE))("compiles %s", (file, fns) => {
|
|
expect(report.skipped[file] ?? {}).toEqual({});
|
|
for (const fn of fns) expect(report.compiled[file]).toContain(fn);
|
|
});
|
|
});
|