fix(css): repair Tailwind v4 color-mix parser crash on dev server

Tailwind v4's content scanner walks the entire project root by default
and emits a CSS rule for every class-shaped token it finds — including
literal class fragments quoted inside .planning report markdown and
docs/ design specs. The bracketed CSS-variable arbitrary-value form
combined with an opacity modifier (e.g. `bg-[var(--name)]/12`)
generates an escaped selector whose `\(...\)\/N` token sequence
crashes Turbopack's CSS parser with `Unexpected token Delim('.')`,
500-ing every page that imports globals.css.

The crash:
    ./src/app/globals.css:1853:27
    Parsing CSS source code failed
      1852 |   .bg-\[var\(...\)\]\/12 {
    > 1853 |     background-color: var(...);
           |                           ^

Three coordinated changes:
- Add `@source not "../../.planning"` and `@source not "../../docs"`
  to globals.css so the scanner skips planning + docs trees. Stops
  fixture-style class names in research/audit reports from poisoning
  the generated CSS.
- Migrate `bg-[var(--dracula-*)]/N` / `ring-[var(--dracula-*)]/N` in
  target-card.tsx to the Tailwind v4 parenthesised shorthand
  `bg-(--dracula-*)/N` / `ring-(--dracula-*)/N`. The shorthand emits
  a clean selector that LightningCSS parses cleanly. Per the Tailwind
  v4 upgrade guide, the bracketed form is deprecated for CSS
  variables.
- Replace `shadow-[0_0_0_1px_var(--dracula-green)]/20` in
  consistency-strip.tsx with `shadow-[0_0_0_1px_color-mix(in_oklab,var(--dracula-green)_20%,transparent)]`.
  The arbitrary shadow with opacity modifier hits the same parser
  issue; encoding the alpha directly in the value via color-mix
  removes the modifier and keeps the visual identical.

Verification: `pnpm dev --port 3030` then `curl -sI /auth/login` ->
HTTP/1.1 200 OK (was 500). Zero CSS parse errors in the dev server
log. Affected tests (targets + api-handler + api/version) still pass
(31/31).
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 15:39:39 +02:00
1 parent 53278ccd87
commit 782731aeda
3 files changed
+38 -8

No files matched your search

+15
View File
@@ -2,6 +2,21 @@
@import "tw-animate-css";
@import "shadcn/tailwind.css";
/*
* Exclude planning + docs directories from Tailwind v4's content scan.
*
* v4's scanner walks the entire project root by default and picks any
* class-shaped token out of every text file. Planning reports and
* design docs often quote literal class-name fragments such as
* `bg-[var(...)]/12` as examples, and the bracketed-CSS-variable form
* combined with an opacity modifier generates an escaped selector that
* Turbopack's CSS parser rejects with `Unexpected token Delim('.')`,
* 500-ing every page. Constraining the scan to source code avoids
* fixture-style strings poisoning the generated CSS.
*/
@source not "../../.planning";
@source not "../../docs";
@custom-variant dark (&:is(.dark *));
@theme inline {
+9 -3
View File
@@ -47,10 +47,16 @@ export interface ConsistencyStripProps {
className?: string;
}
// An arbitrary shadow utility with an opacity modifier (a "/20" suffix
// on the bracketed value) hits the same Turbopack CSS-parser issue as
// the bracket-form CSS-variable utilities — see the comment in
// target-card.tsx for the full diagnosis. The shadow's alpha is now
// encoded inside the value via color-mix so the opacity modifier is
// no longer needed and the bracketed form parses cleanly.
const BAND_STYLES: Record<NonNullable<ConsistencyBand>, string> = {
in: "bg-[var(--dracula-green)] border-[var(--dracula-green)] shadow-[0_0_0_1px_var(--dracula-green)]/20",
near: "bg-[var(--dracula-orange)] border-[var(--dracula-orange)]",
out: "bg-[var(--dracula-red)] border-[var(--dracula-red)]",
in: "bg-(--dracula-green) border-(--dracula-green) shadow-[0_0_0_1px_color-mix(in_oklab,var(--dracula-green)_20%,transparent)]",
near: "bg-(--dracula-orange) border-(--dracula-orange)",
out: "bg-(--dracula-red) border-(--dracula-red)",
};
const BAND_ARIA: Record<NonNullable<ConsistencyBand>, string> = {
+14 -5
View File
@@ -202,10 +202,19 @@ function statusGroupForCategory(category: string): "in" | "near" | "out" {
return "near";
}
// Use the Tailwind v4 parenthesised CSS-variable shorthand
// (bg-VAR-PAREN-FORM-/N) instead of the legacy bracketed VAR-form. The
// bracketed form combined with an opacity modifier emits an escaped
// class selector that Turbopack's CSS parser rejects with
// "Unexpected token Delim('.')" because the scanner picks the literal
// string out of source files (including this comment) and emits a CSS
// rule for it. The parenthesised shorthand produces a clean selector
// that parses cleanly. See the Tailwind v4 upgrade guide for the
// migration from bracket-form to paren-form for CSS-variable values.
const STATUS_PILL_STYLES: Record<"in" | "near" | "out", string> = {
in: "bg-[var(--dracula-green)]/12 text-[var(--dracula-green)] ring-[var(--dracula-green)]/30",
near: "bg-[var(--dracula-orange)]/14 text-[var(--dracula-orange)] ring-[var(--dracula-orange)]/30",
out: "bg-[var(--dracula-red)]/12 text-[var(--dracula-red)] ring-[var(--dracula-red)]/30",
in: "bg-(--dracula-green)/12 text-(--dracula-green) ring-(--dracula-green)/30",
near: "bg-(--dracula-orange)/14 text-(--dracula-orange) ring-(--dracula-orange)/30",
out: "bg-(--dracula-red)/12 text-(--dracula-red) ring-(--dracula-red)/30",
};
interface TargetClassification {
@@ -611,11 +620,11 @@ export function TargetCard({
)}
{showStreak && (
<span
className="text-foreground inline-flex items-center gap-1.5 rounded-full bg-[var(--dracula-green)]/12 px-2 py-0.5 font-medium ring-1 ring-[var(--dracula-green)]/30"
className="text-foreground inline-flex items-center gap-1.5 rounded-full bg-(--dracula-green)/12 px-2 py-0.5 font-medium ring-1 ring-(--dracula-green)/30"
data-slot="target-streak"
>
<span
className="size-1.5 rounded-full bg-[var(--dracula-green)]"
className="size-1.5 rounded-full bg-(--dracula-green)"
aria-hidden="true"
/>
{t("targets.card.streak", { count: String(target.streakDays) })}