fix(design): the table header takes the B9 fade utility, and every size is dvh, not vh (E28 S3d4)

The table header carried an arbitrary-value copy of fade-below; a contract refuses a hand gradient of a surface colour. 100vh is the viewport with a tablet browser's toolbar retracted, so h-screen / min-h-screen / static vh hid the last rows under it; a contract holds dvh (the Plex player stage changes with S3f, where it is played on the tablet).
This commit is contained in:
2026-09-26 00:39:10 +02:00
parent eddeca82d3
commit 4646aae320
15 changed files with 80 additions and 17 deletions
@@ -0,0 +1,30 @@
import { readdirSync, readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
// E28 S3d4 (B9). The header + fade edge is one recipe, the `fade-below` / `fade-below-panel` /
// `fade-above-panel` utilities in index.css: a crisp band and a 1rem gradient of the colour under
// it. The table header carried its own arbitrary-value copy of it, which a change to the recipe (its
// height, its colour on a panel) would have left behind.
function sources(): Array<[string, string]> {
const walk = (u: URL, prefix: string): Array<[string, string]> =>
readdirSync(u, { withFileTypes: true }).flatMap((e) => {
if (e.isDirectory()) return walk(new URL(`${e.name}/`, u), `${prefix}${e.name}/`);
if (!/\.tsx?$/.test(e.name) || e.name.includes(".test.")) return [];
return [
[`${prefix}${e.name}`, readFileSync(new URL(e.name, u), "utf-8")] as [string, string],
];
});
return walk(new URL("../", import.meta.url), "");
}
describe("the fade edge", () => {
it("is the utility, never an arbitrary gradient of a surface colour", () => {
const copies = sources()
.filter(([, src]) =>
/linear-gradient\(to_(?:bottom|top),var\(--(?:bg|surface|card)\)/.test(src),
)
.map(([name]) => name);
expect(copies).toEqual([]);
});
});
@@ -0,0 +1,33 @@
import { readdirSync, readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
// E28 S3d4 (E18.5's `h-screen` → `dvh`). `100vh` is the viewport with a mobile browser's toolbars
// RETRACTED: on a tablet with the toolbar showing, an `h-screen` shell runs its bottom edge (the
// last rows, a dialog's footer) under the toolbar. `dvh` follows the toolbar. A static `vh` size is
// the same trap at a smaller scale, so the app sizes by `dvh` everywhere.
function sources(): Array<[string, string]> {
const walk = (u: URL, prefix: string): Array<[string, string]> =>
readdirSync(u, { withFileTypes: true }).flatMap((e) => {
if (e.isDirectory()) return walk(new URL(`${e.name}/`, u), `${prefix}${e.name}/`);
if (!/\.tsx?$/.test(e.name) || e.name.includes(".test.")) return [];
return [
[`${prefix}${e.name}`, readFileSync(new URL(e.name, u), "utf-8")] as [string, string],
];
});
return walk(new URL("../", import.meta.url), "");
}
// The Plex player's stage is sized with the 80vh × 1.25 zoom trick. It changes in S3f, where the
// players are re-fitted for touch and verified by playing them on the tablet.
const PENDING_S3F = new Set(["components/PlexPlayer.tsx"]);
describe("viewport units", () => {
it("are dynamic: no h-screen / min-h-screen, no static vh", () => {
const hits = sources()
.filter(([name]) => !PENDING_S3F.has(name))
.filter(([, src]) => /\b(?:min-|max-)?h-screen\b|\d(?:\.\d+)?vh\b/.test(src))
.map(([name]) => name);
expect(hits).toEqual([]);
});
});