fix(gate): knip sees the watch entry, the build must emit watch.html, and two hand copies are held

- The raw-source contract tests read files from disk (rawSources.testkit)
  instead of import.meta.glob, which knip counted as importing every src file —
  its unused-files lane could never name an orphan. knip now names
  src/watchMain.tsx as an entry (registered in docs/gates.md).
- vite build fails when index.html or watch.html is not emitted.
- The watch-entry import walk follows dynamic import() too.
- audit_metrics.py's plan-code patterns are held equal to the guard's.
- CrashBoundary is domain-free: it moves into components/ui.
This commit is contained in:
2026-10-06 03:19:33 +02:00
parent b20e9a3947
commit d44d8d6dda
19 changed files with 119 additions and 55 deletions
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { readSources } from "./rawSources.testkit";
// Every `useMutation` NAMES what failed (owner decision: the toast's title says what, the
// line under it why). The app-wide reporter (lib/queryClient) reports a mutation that names nothing
@@ -36,11 +37,7 @@ export function mutationCalls(source: string): string[] {
}
function unnamed(): { hits: string[]; mutations: number } {
const files = import.meta.glob("../**/*.{ts,tsx}", {
query: "?raw",
import: "default",
eager: true,
});
const files = readSources(__dirname, "../**/*.{ts,tsx}");
const hits: string[] = [];
let mutations = 0;
for (const [rel, source] of Object.entries(files)) {
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { readSources } from "./rawSources.testkit";
// Every push that opens a new view over the current one remembers the list item it was opened from
// (`lib/history.rememberNavPosition`), so Back hands focus back to that item. A push that
@@ -10,11 +11,10 @@ import { describe, expect, it } from "vitest";
// The assertion pins the SET of offending sites and checks the scan found pushes at all: a guard whose
// glob stops matching is green over nothing.
const files = import.meta.glob(["../**/*.ts", "../**/*.tsx", "!../**/*.test.*"], {
query: "?raw",
import: "default",
eager: true,
}) as Record<string, string>;
const files = readSources(__dirname, ["../**/*.ts", "../**/*.tsx", "!../**/*.test.*"]) as Record<
string,
string
>;
const EXEMPT: Record<string, string> = {
"lib/history.ts#_ov": "the overlay stack: an overlay entry opens no view over the page",
@@ -1,14 +1,11 @@
import { describe, expect, it } from "vitest";
import { readSources } from "./rawSources.testkit";
// Every infinite list's read-ahead waits while the list fetches ANYTHING and runs again when the fetch
// settles. A read-ahead that only checks `isFetchingNextPage` calls during a background
// refetch, joins it through NEXT_PAGE and loads no page: the grid stalls at its loaded end until the
// reader scrolls the range. The jsdom virtualizer measures nothing, so the wiring is pinned in source.
const files = import.meta.glob(["../components/**/*.tsx", "!../components/**/*.test.tsx"], {
query: "?raw",
import: "default",
eager: true,
});
const files = readSources(__dirname, ["../components/**/*.tsx", "!../components/**/*.test.tsx"]);
const source = (name: string) => {
const hit = Object.entries(files).find(([path]) => path.endsWith(`/${name}`));
if (!hit) throw new Error(`no ${name}`);
@@ -89,6 +89,20 @@ describe("plan codes", () => {
expect(clean.filter(isPlanCode)).toEqual([]);
});
it("are what the audit metric counts by (its copy of the patterns is held equal here)", () => {
const py = readFileSync(new URL("scripts/audit_metrics.py", REPO), "utf8");
// A pattern there is `NAME = re.compile(r"…" r"…", re.ASCII)`: its raw literals, joined.
const pattern = (name: string) => {
const start = py.indexOf(`${name} = re.compile(`);
const call = py.slice(start, py.indexOf("re.ASCII", start));
return [...call.matchAll(/r(["'])(.*?)\1/g)].map((m) => m[2]).join("");
};
// JavaScript's source escapes the slash a regex literal needs; Python's has none to escape.
const js = (re: RegExp) => re.source.split(String.raw`\/`).join("/");
expect(pattern("NOT_CODES")).toBe(js(NOT_CODES));
expect(pattern("PLAN_CODE")).toBe(PLAN_CODES.map(js).join("|"));
});
it("are not cited in the code", () => {
const files = tracked();
expect(files.length).toBeGreaterThan(1000); // a scan that reads nothing is green over nothing
+6 -8
View File
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
import { expect, it } from "vitest";
import { cardImage } from "./plexImage";
import { readSources } from "./rawSources.testkit";
// A card's srcset names each Plex cut by its width, and the browser picks one by that width. The
// widths are the image proxy's (backend `app/plex/imgcache.py` SIZES), with no wire format between
@@ -51,14 +52,11 @@ it("names each backdrop cut by the width the proxy cuts it at", () => {
// proxy's; the SET is pinned, so a new one fails here. Every <img>'s src counts, wherever it sits
// among the attributes and whatever its variable is called: the first cut of this test read only a
// leading `src={…thumb…}` and missed two of the three sites it was written for.
const plexFiles = import.meta.glob(
[
"../components/{Plex,plex}*.tsx",
"../components/{Home,PosterTile,UpNextCard}.tsx",
"!../components/**/*.test.tsx",
],
{ query: "?raw", import: "default", eager: true },
);
const plexFiles = readSources(__dirname, [
"../components/{Plex,plex}*.tsx",
"../components/{Home,PosterTile,UpNextCard}.tsx",
"!../components/**/*.test.tsx",
]);
const ALLOWED = new Set([
"plexCards.tsx pic.src", // cardImage's own src (srcSet/sizes beside it): the card cut
"plexCards.tsx c.thumb", // cast photos: the person-image route, not the image proxy
+33
View File
@@ -0,0 +1,33 @@
import { globSync, readFileSync } from "node:fs";
import { matchesGlob, relative, resolve, sep } from "node:path";
// The source text a contract test scans, read from disk. Not `import.meta.glob(…, { query: "?raw" })`:
// knip reads every such glob as an import of each file it matches, and two tests globbed all of src —
// so its unused-files lane counted every source file as used and could never name an orphaned one
// (the shared-link entry was "used" only by those globs).
/** Every file matching `patterns` (relative to `dir`, the calling test's directory; a leading "!"
* excludes) with its text, keyed the way `import.meta.glob` keyed it: "./x.tsx", "../lib/y.ts". */
export function readSources(dir: string, patterns: string | string[]): Record<string, string> {
const list = typeof patterns === "string" ? [patterns] : patterns;
const include = list.filter((p) => !p.startsWith("!"));
const exclude = list.filter((p) => p.startsWith("!")).map((p) => key(p.slice(1)));
const out: Record<string, string> = {};
for (const pattern of include) {
for (const rel of globSync(pattern, { cwd: dir })) {
// Canonical, as Vite's: "../ui/Modal.tsx" from inside ui/ is "./Modal.tsx", matched once.
const k = key(relative(dir, resolve(dir, rel)));
// An exclusion names the file either way: "!../**/*.test.*" as the pattern spelled it
// ("../lib/x.test.ts"), "!./**" by its canonical key ("./x.test.ts" from inside lib/).
if (exclude.some((x) => matchesGlob(key(rel), x) || matchesGlob(k, x))) continue;
out[k] = readFileSync(resolve(dir, rel), "utf8");
}
}
return out;
}
/** A path relative to the test's directory, as `import.meta.glob` spells it. */
function key(path: string): string {
const posix = path.split(sep).join("/");
return posix.startsWith(".") ? posix : `./${posix}`;
}