Tailwind 4 ships its own Vite plugin and no longer needs PostCSS plumbing.
Drop postcss/autoprefixer/postcss-import/postcss-nesting and the .postcssrc.json,
wire @tailwindcss/vite into vite.config.ts, replace the @tailwind directives
with @import "tailwindcss", and convert the JS tailwind.config.cjs theme
into a CSS @theme block in shared.css. An @source directive points back at
the SSR templates so the Jinja-rendered HTML in templates/ still gets scanned
for utility classes.
The third-party CSS (compound design tokens, compound-web, fontsource) moves
into a new vendor.css entrypoint, loaded ahead of shared.css from main.tsx,
storybook, and base.html. Tailwind v4's @import bundler silently drops the
nested `@import url(...) layer(cpd-base) screen` statements inside the
compound-design-tokens.css barrel, so we have to keep those imports out of
any file that contains Tailwind directives and let Vite's normal CSS pipeline
resolve them instead.
They are very small and don't need an extra request to load. This is useful as
well for making the snapshots not dependant on where the project is installed
Sets up a pnpm workspace at the repo root with the single existing
frontend/ package, pinned via packageManager to pnpm@11.1.0. The new
pnpm-workspace.yaml enables three pnpm 11 defaults explicitly so they
can't silently be relaxed:
- trustPolicy: no-downgrade (override via trustPolicyExclude)
- strictDepBuilds: true (override via allowBuilds)
- strictPeerDependencies: true (override via peerDependencyRules.allowedVersions)
allowBuilds permits the @swc/core, esbuild and msw postinstalls (native
binaries / service worker setup) and explicitly denies @scarf/scarf
telemetry. trustPolicyExclude lists semver@6.3.1, which a transitive of
@babel/core publishes without provenance even though earlier versions
had it.
The lockfile is imported from the previous package-lock.json, so locked
versions stay identical to npm — no dependency upgrades sneak in here.
@graphql-typed-document-node/core is added as an explicit devDependency
because @graphql-codegen/client-preset's generated code imports it
directly and pnpm doesn't hoist transitive deps.
The frontend/.npmrc engine-strict=true setting moves to pnpm-workspace.yaml
as engineStrict: true.