From 782731aeda79604cc81328ab672dbb3cb833e577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 14 May 2026 15:39:39 +0200 Subject: [PATCH] fix(css): repair Tailwind v4 color-mix parser crash on dev server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- src/app/globals.css | 15 +++++++++++++++ src/components/targets/consistency-strip.tsx | 12 +++++++++--- src/components/targets/target-card.tsx | 19 ++++++++++++++----- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 28587c293..4dd6cac59 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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 { diff --git a/src/components/targets/consistency-strip.tsx b/src/components/targets/consistency-strip.tsx index 42355fc6d..cfde3f643 100644 --- a/src/components/targets/consistency-strip.tsx +++ b/src/components/targets/consistency-strip.tsx @@ -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, 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, string> = { diff --git a/src/components/targets/target-card.tsx b/src/components/targets/target-card.tsx index db33a22be..1bc0c22a7 100644 --- a/src/components/targets/target-card.tsx +++ b/src/components/targets/target-card.tsx @@ -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 && (