- docs/DESIGN.md: the written source of truth for the UI. Decided chapters (principles, device adaptation, action placement, text stays selectable, interaction rules, copy, anti-patterns, decision log); controls and surfaces are marked Pending until the owner picks from the lab. - /design-lab (dev only): renders each open decision's variants with the app's real CSS, themes and font scale. Batch A = six control decisions. - devOnlyGuard: a production build fails if anything under src/design-lab reaches the bundle (seen red with an unguarded import). - Size contract: tablet portrait + landscape and up, replacing the 1024x768 floor with its never-built "too small" message.
30 lines
1.2 KiB
TypeScript
30 lines
1.2 KiB
TypeScript
import type { Plugin } from "vite";
|
|
|
|
// The design lab (`src/design-lab/`) is a dev-only page: main.tsx reaches it behind
|
|
// `import.meta.env.DEV`, which Vite folds to `false` in a production build, so the dynamic import
|
|
// is dead code and the chunk is never emitted. That rule used to be a sentence; this makes it a
|
|
// build failure. If a future edit imports the lab unguarded (a static import, a shared helper, a
|
|
// re-export), the production build refuses instead of shipping the lab to every visitor.
|
|
const DEV_ONLY_DIR = "/src/design-lab/";
|
|
|
|
export function devOnlyGuard(): Plugin {
|
|
return {
|
|
name: "siftlode-dev-only-guard",
|
|
apply: "build",
|
|
generateBundle(_options, bundle) {
|
|
for (const [fileName, output] of Object.entries(bundle)) {
|
|
if (output.type !== "chunk") continue;
|
|
const leaked = Object.keys(output.modules).find((id) =>
|
|
id.replaceAll("\\", "/").includes(DEV_ONLY_DIR),
|
|
);
|
|
if (leaked) {
|
|
this.error(
|
|
`dev-only module ${leaked} reached the production bundle (${fileName}). ` +
|
|
"Import src/design-lab only behind `import.meta.env.DEV`.",
|
|
);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
}
|