- PageShell names the page with a visually hidden h1; a view with its own title renders it as PageHeading and the hidden one steps aside (contract: no hand <h1> inside the shell). - Section captions are SectionLabel headings (h2, h3 in a dialog), ui/form Section included. - "Skip to content" is the first Tab stop and focuses the scroller's <main>. - Below 600px of height the band's filter row folds into "Filters · n" (Feed, Plex, server log), the same row shown in place. - The band holds controls only: Downloads and Notifications lose their title cards, the Config and channel-discovery intros scroll with the content, the Stats tabs and the audit pager join the band, and the server log's toolbar sticks with the fade under it.
40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
import { readdirSync, readFileSync } from "node:fs";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
// E28 S3d4 (B4, UX-15). One `h1` per page: the shell's hidden page name, or a view's own visible
|
|
// title through `PageHeading`, which makes the shell's step aside. A hand-written `<h1>` inside the
|
|
// shell would be a second one, beside the page name the shell already put there.
|
|
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), "");
|
|
}
|
|
|
|
// Pages that render OUTSIDE the app shell own their h1 (no shell, no hidden page name).
|
|
const STANDALONE = new Set([
|
|
"components/Welcome.tsx",
|
|
"components/WatchPage.tsx",
|
|
"components/legal/LegalLayout.tsx",
|
|
"design-lab/DesignLab.tsx",
|
|
// The shell's hidden h1 and the primitive itself.
|
|
"components/ui/PageShell.tsx",
|
|
"components/ui/Headings.tsx",
|
|
]);
|
|
|
|
describe("the page heading", () => {
|
|
it("is written as PageHeading inside the shell, never as a hand <h1>", () => {
|
|
const own = sources()
|
|
.filter(([name]) => !STANDALONE.has(name))
|
|
.filter(([, src]) => /<h1[\s>]/.test(src))
|
|
.map(([name]) => name);
|
|
expect(own).toEqual([]);
|
|
});
|
|
});
|