Skip to content

Build the website without webpack's cache so new Tailwind classes ship - #1425

Merged
MaxGhenis merged 6 commits into
mainfrom
fix/next-build-css-cache
Oct 26, 2026
Merged

MaxGhenis merged 6 commits into
mainfrom
fix/next-build-css-cache

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Why

policyengine.org is serving stale CSS. The live stylesheet (/_next/static/css/b76c71cfd6868604.css, fetched 2026-10-25) is missing twelve Tailwind classes that main's website source uses. A clean next build of the same source has all of them. The visible damage:

  • Homepage tool cards (HomeToolsPreview, from Link CliffWatch and the OBBBA explorer from the US homepage and main nav #1421 on 10/24). The cards have no padding (p-3xl), the grid has no gap (gap-2xl), and the 40px icon box (w-10, bg-primary-50) stretches to the card's full width with no background. On policyengine.org/us the card body has no padding, the grid gap is normal, and the icon box spans the card (490 px at a 1024 px window).
  • Supporter cards (SupporterCard, from 10/14). sm:items-center doesn't apply, so each card's header (logo and text) isn't vertically centered: the header computes align-items: normal, measured at a 1024 px window. For a supporter with no logo, the card has a spacer, tw:hidden tw:sm:block tw:w-[200px], that lines its text up with the logo cards. Production has no sm:block rule, so the spacer stays hidden.

The full list of missing classes:

  • HomeToolsPreview: bg-primary-50, focus-visible:outline-none, focus-visible:shadow-[0_0_0_2px_var(--color-primary-500)], gap-2xl, mt-2xl, p-3xl, sm:min-h-[280px], tracking-[0.06em], w-10.
  • SupporterCard: sm:block, sm:items-center, w-[200px].

#1424's preview has the same problem: its CSS lacks the focus-outline classes that PR adds.

Cause

Any change at all leaves the CSS stale, not only changes to server-rendered files, as this PR first said. The round-1 review found the trigger: symlinked data files.

  1. Tailwind makes website/src a context dependency of globals.css. Its PostCSS plugin emits a dir-dependency message for each source root. Next 15.5.12's PostCSS loader passes each one to this.addContextDependency (build/webpack/loaders/postcss-loader/src/index.js:91-92).

  2. The webpack bundled with Next 15.5.12 can't snapshot website/src.

    • website/src/data holds six symlinks to files in app/src/data: apps.json, citations.json, events.json, featuredTools.json, posts/authors.json and posts/posts.json.
    • Asked for a production-style snapshot of website/src, webpack's FileSystemInfo returns null, with ENOTDIR: not a directory, scandir '…/app/src/data/posts/posts.json'. It reads each symlink's target as a directory.
    • website/scripts, calculator-app/src, calculator-app/scripts and app/src snapshot fine.
  3. The cached CSS then has no record of its inputs. After taking a snapshot, webpack's NormalModule drops the module's file, context and missing dependency lists, and keeps only the snapshot, which here is null. I read that code.

    • The review traced the rest, through mini-css-extract-plugin's importModule. The outer globals.css module records none of the scanned files, and not globals.css either, so on a warm build its cached CSS still counts as valid.
  4. Reproduced on the real website (this branch's base, with main's config, in a scratch copy). Three builds:

    • a cold build;
    • a warm build after adding tw:mt-[53px] to the client component HomeBlogPreview;
    • a warm build after appending a rule to globals.css.

    All three emitted the same f4d325ca88a07d63.css, so neither change reached the CSS.

  5. Why production isn't stale forever. Next rotates its build encryption key every 14 days (server/app-render/encryption-utils-server.js:28). The key is part of webpack's cache version (serverReferenceHashSalt, build/webpack-config.js:1882), so the whole cache is rebuilt at least that often. The review found that the live stylesheet's classes exactly match main's source at 9c98884 (10/14): 586 classes.

Corrections to the first version of this PR:

Change

  • Production builds skip webpack's persistent cache (website/next.config.ts): config.cache = false when !dev.
    • The fix holds by construction: with cache: false, webpack never reads .next/cache/webpack. It doesn't depend on the symlinks, and it would cover any other way a snapshot fails.
    • Next calls the hook for the client, server and edge compilations (build/webpack-build/impl.js:150-176, build/webpack-config.js:2005).
    • next dev runs on Turbopack, which doesn't call the hook. It now prints "Webpack is configured while Turbopack is not", which the comment says is expected.
  • Build workers kept. A webpack hook makes Next run every compilation in the main process unless experimental.webpackBuildWorker is set (build/index.js:835). This PR sets it, so the website builds in the same processes as on main.
  • Every build checks its CSS (scripts/check-built-css.mjs). It runs after next build:
    • It compiles the app's stylesheet with Tailwind, scanning from the app's directory as the build does, and collects the class names in .next/static/css.
    • It fails the build if a class the source uses is missing, or if the build has a tw: utility the source doesn't use.
    • It compares class names, not CSS. Stale CSS that differs only in a value, such as a theme colour, passes, and so does a hand-written rule the source has since dropped. That doesn't matter for the website, which no longer reads the cache. For the calculator, see below.
    • Classes from other stylesheets are allowed, such as the website's RichTextBlock.css.
    • The website's build.sh and the calculator's build script both run it. CI and Vercel run those scripts.
    • It takes about half a second locally. On Vercel it prints its result 0.9 to 1.0 s after Next's last line of output (four builds).
  • Old webpack packs dropped. build.sh deletes .next/cache/webpack before building. Production builds no longer read it, but Vercel restored and re-uploaded it. The website's build-cache upload went from 444.70 MB to 341.01 MB.

Not changed: the calculator's cache

Webpack can snapshot all three of the calculator's Tailwind source directories, so it keeps its cache. Its production CSS (9ec0e33bef9ec190.css) is byte-identical to a clean local build, and a probe class added to a server-rendered file reached the CSS on a warm build.

Two tests keep it that way:

  • The cause (next-build-cache.test.ts). The test takes the directories webpack must snapshot from Tailwind's own dir-dependency messages, so it can't drift from the config: app/src, calculator-app/src and calculator-app/scripts. Then it asks the webpack bundled with Next to snapshot each one, with a production build's snapshot options and Next's managedPaths, and fails if any snapshot comes back null. So a symlink webpack would read as a directory fails it, including one behind a directory link, without the test re-deriving webpack's rules. The managedPaths are copied from Next's build config, and another test fails if Next's copy of that block changes. For the website, the same test expects exactly website/src to fail, so it can see a failure, and it will say so once a Next upgrade fixes the bug.
  • The symptom. The class check runs after the calculator's next build. It catches stale CSS once the source adds a class or drops a Tailwind utility. It misses a change to a value alone, and a hand-written class the source dropped.

Cost

Website builds on Vercel, from their build logs. The build step runs from the log's Running "bash scripts/vercel-build.sh" line to its "Build Completed" line, by the log's timestamps. Round 2's body took 01815e5's first build step as 41 s and round 3's as 42 s: it is 41.5 s, and Vercel's own summary line rounds it to 42 s. Every row below is now the timestamp difference, to a tenth of a second.

Build Webpack cache Build workers Compile Build step
main, production (5bccbf2) restored on 8.0 s 40.2 s
01815e5, this PR's first version: 4 builds off off 20.7, 23.5, 26.2, 34.4 s 41.5, 48.6, 52.7, 72.6 s
52e0dd7 off on 34.4 s 69.1 s
5213962: 4 builds off on 29.6, 22.1, 32.4, 23.2 s 64.3, 47.1, 67.1, 49.1 s
c029781 off on 24.2 s 49.7 s
  • The cache. Builds without it compiled in 21 to 34 s, against 8.0 s for main's last production build, which restored it. So turning the cache off costs about 13 to 26 s of compile per website build. Their build step took 42 to 73 s, against 40 s: 1 to 32 s more. That is the extra compile time, plus or minus up to 11 s from the rest of the build (21 to 38 s here, against main's 32 s), which moves with the machine.
  • The build workers: no cost I can measure.
    • Mechanism. I read this in Next 15.5.12. With workers, Next starts a new process for each compilation in turn: server, edge server, then client. Each one reloads next.config (build/webpack-build/index.js:93, impl.js:340-355). In one process, the three compilations share one input file system (impl.js:199-251). So workers could cost time.
    • Measurement. I redeployed the 01815e5 preview (no workers) and the 5213962 preview (workers) three times, each pair at the same time, with the same restored build cache. The two also differ in build.sh: 5213962's runs the check and deletes the old packs, which 01815e5's doesn't. Both add time to the builds with workers, so they can't hide a cost. Compile times were 23.5 vs 22.1 s, 26.2 vs 32.4 s, and 34.4 vs 23.2 s. Build steps were 49 vs 47 s, 53 vs 67 s, and 73 vs 49 s.
    • Reading. Workers were faster in two pairs of three. The medians are 26.2 vs 23.2 s compile and 53 vs 49 s build step. Machine speed swamps any difference: in the third pair, the no-worker build was slow at type checking and static generation too (10.8 and 16.4 s, against 6.7 and 9.0 s).
    • Decision. I keep the workers, so the website builds in the same process layout as on main. The script and its output are in ~/reviews/app-v2-pr1425/r3-worker-cost/.
  • The check adds about a second.
  • The calculator's build is unchanged, apart from the check.

Invariants

  1. Enforced by every build, which fails otherwise. In either app, the built CSS has every class of a fresh Tailwind compile of the app's stylesheet, and exactly its tw: utilities. This is about class names, not values.
  2. Website production builds don't use webpack's cache. Tested with isServer false and true. The edge compilation calls the hook with isServer: true.
  3. Selector parsing (property test, 500 runs). For generated class names in generated selector shapes, the check returns exactly those names, in order.
    • Names: half from any script, half from the ASCII that class names and their escapes are made of (digits, -, backslashes, quotes, brackets, tab, U+0001, U+007F).
    • Escaping: CSSOM's CSS.escape, or every character as a hex escape.
    • Shapes: compounds of one or two classes; combinators; :is, :not, :where and :has, around one class or a list of two; pseudo-classes and pseudo-elements; ID and type selectors.
    • Not counted: dots inside strings, and escaped dots in IDs and attribute values.
    • Keyframe steps aren't classes.
    • Examples pin the edge cases: CR LF, a tab or a form feed after a hex escape; upper-case hex; an escaped NUL; an escaped quote inside a string; and comments, which the generator doesn't draw: /*! … */, an unclosed one, and one followed by *.
  4. Comparison (property test). For any two class sets, missing is exactly fresh minus built. The classes only the build has split by prefix into stale utilities and other CSS.
  5. The calculator can keep its cache. Webpack, as Next bundles and configures it for production, can snapshot every directory Tailwind reports for the calculator's stylesheet. For the website it can't snapshot website/src, as a positive control. The snapshot options come from two places, and a test pins each: webpack's production default for snapshot.module, and the block in Next's build/webpack-config.js that sets managedPaths and nothing else outside Yarn PnP. Nothing after that block, in Next or in either app's webpack hook, sets them in Next 15.5.12 (read by the round-5 review), but no test checks that yet; a follow-up PR adds one.
  6. The command line runs however it's reached from a file. Through a symlinked path, with or without --preserve-symlinks-main, it still exits 2 without arguments and 1 for a stale build. Imported from node -e with an argument, it runs nothing and doesn't throw. Code read from stdin has no path to compare, so it runs nothing; no build runs it that way.
  7. The fresh compile is optimized, whatever the check's NODE_ENV. A rule Lightning CSS rejects is dropped from it, as the build drops it. Tailwind optimizes by default when NODE_ENV is "production", which would hide a check that didn't ask for it, so the test sets NODE_ENV to "development" for that compile.

Verification

  • End to end: real website builds in a scratch copy of this branch.

    • Main's config, cold: the check passes (598 classes, plus 1 from RichTextBlock.css).
    • Main's config, warm, after adding tw:mt-[61px] to HomeBlogPreview: the CSS lacks it, and the check exits 1, naming it.
    • This PR's build.sh, twice, with the probe changed to tw:mt-[67px] in between. Both builds pass, both stylesheets contain the probe, and no .next/cache/webpack is left.
  • Production's live stylesheet: the check reports exactly the twelve missing classes listed above, and nothing else.

  • Calculator, local build: passes the check (783 classes).

  • Vercel at 52e0dd7, 5213962 and c029781: both apps' builds ran the check and passed. The website printed "has all 598 classes of a fresh compile of src/app/globals.css, plus 1 from other stylesheets"; the calculator, "has all 783 classes".

  • Website checks at bb6f4ae: full suite (49 files, 454 tests), and the two config test files again with NODE_ENV=production (42 tests). CI at bb6f4ae: lint, format, type checking and the full suite on Node 26 (49 files, 454 tests).

  • Mutants (mutants11.py, run against the config and check tests): 44 of 46 fail at least one test, and the four control trees pass. Round 4's set is unchanged (41 of 42); the four new ones are listed last.

    • Config: the cache kept; build workers not requested.
    • Wiring: build.sh skips the check, runs it before building, or keeps the old packs; the calculator skips the check.
    • Parsing:
      • names stop at escapes;
      • hex escapes aren't decoded, keep their trailing space, read only five digits, or take CRLF as two characters;
      • invalid code points and hex-escaped NULs aren't replaced;
      • backslash-newline is read as an escape;
      • escapes outside a class are read as text;
      • non-ASCII characters end a name;
      • strings aren't skipped, or end at an escaped quote;
      • comments in a selector aren't skipped, or end one character early;
      • only a space ends a hex escape, or upper-case hex digits aren't read;
      • keyframe steps are counted, or only unprefixed @keyframes are skipped.
    • Comparison and command line:
      • missing classes computed backwards;
      • stale utilities pass;
      • Tailwind scans the working directory instead of the app;
      • nested CSS files are ignored, or only the first file is read;
      • the no-utilities and no-files guards are removed;
      • the entry guard compares the unresolved process.argv[1], leaves the module's URL unresolved, or lets realpathSync throw;
      • the fresh compile isn't optimized. The class sets are still identical both ways for both apps (598 and 783), but the fixture's rule that Lightning CSS rejects now tells them apart.
    • Source directories, each a tree change in a cloned repo:
      • a file link in calculator-app/src, in calculator-app/scripts, deep in app/src, or in calculator-app/src/node_modules;
      • a directory link in calculator-app/src to website/src/data, which holds file links (the round-3 review's case);
      • the test reading a null snapshot as fine.
    • Controls that pass, because webpack snapshots them fine: a dangling link, a link to a missing file, and a file link in a hidden directory, which webpack skips.
    • The survivors are W2 and E1. W2 gives webpack hash-only snapshot options. On every tree above, webpack's verdict is the same with {timestamp, hash}, {timestamp} or {hash}, so it's equivalent here. A separate test pins the options a production build uses. E1 is expected: it shows that the new NODE_ENV stub is what kills N4 under production (below).
    • New in round 5:
      • the check not asking for optimize, run with NODE_ENV=production: killed, now that the test sets NODE_ENV. Without that line it survives, so the line is what kills it;
      • the oracle's managedPaths emptied, or its regex changed: both killed by the new test of Next's config;
      • a control: a directory link in calculator-app/src to a package under node_modules that holds a file link. It passes, because webpack snapshots the package by its package.json.
    • One correction. Round 2's mutant for a file link in calculator-app/src pointed at app/src/data/apps.json, which doesn't exist (the file is app/src/data/apps/apps.json). So it tested a dangling link, and the old walker failed it only because statSync threw. Its target is fixed here, and the link to a missing file is now its own control.
  • First version's preview (01815e5), at a 1024 px window, measured in the browser:

    Measure This PR's preview policyengine.org
    /us tools grid: gap 24px normal
    /us CliffWatch card: body padding 32px none
    /us CliffWatch card: icon box 40px, rgb(230, 255, 250) 490px wide, transparent
    /us/supporters: the 14 elements with sm:items-center align-items: center normal
  • Independent review, round 1 (Opus 5.5, at 01815e5) asked for ten changes:

    1. the comments' mechanism;
    2. the Cause section;
    3. the build workers;
    4. evidence that tells the fix from a one-time cache bust;
    5. a check of the built CSS;
    6. the calculator;
    7. over-specified tests;
    8. the Turbopack warning;
    9. the dead packs;
    10. the empty dev-build invariant.

    All ten are addressed here. For finding 7, I kept a dev case, renamed for what it covers: next dev without --turbopack still calls the hook, and keeping the cache there keeps rebuilds incremental.

  • Independent review, round 2 (Opus 5.5, at 5213962) asked for six changes. All six are addressed at c029781:

    1. The check compares class names, not CSS. The config comment, the script's header and this body now say so. The calculator also gets a test of the cause, beside the check of the symptom: no directory Tailwind reads for its stylesheet holds a symlink to a file.
    2. The cost reading. I measured three paired redeploys instead; see Cost.
    3. Run through a symlinked path, the script did nothing and exited 0. The entry guard now resolves process.argv[1], with a test.
    4. The selector property test rarely drew escapes. It now draws from an ASCII-heavy alphabet as well, with :has and two-class wrappers, plus the three examples the review gave. The mutants N1 to N3 now fail.
    5. Comments in a selector are skipped.
    6. Two inaccuracies. The comment on Next's PostCSS and the failure message, which now says how to recover on Vercel.
  • Independent review, round 3 (Opus 5.5, at c029781) asked for one should-fix and four nits. All five are addressed at 348c40d:

    1. The calculator's cause test missed a file link behind a directory link, which webpack follows. The test now asks webpack itself for its snapshot verdict, instead of walking for symlinks.
    2. The entry guard failed open under --preserve-symlinks-main and threw when imported from node -e with an argument. It now compares real paths on both sides and can't throw, with a test for each case.
    3. The cost reading. The build step doesn't always grow by more than the compile. Every row is now one measure, and the arms' other difference (the check and the pack delete) is stated.
    4. Four surviving mutants. Three are now killed by new examples, the fourth by the new guard tests. optimize: true stays, and the fixture shows why.
    5. Overstated wording, in build.sh, the script's header and failure message, this body's calculator section and invariant 3.
  • Independent review, round 4 (Opus 5.5, at 348c40d) approved, with four nits. It instrumented real next builds of the calculator and a minimal app, and on every tree that reached the CSS, the build's snapshot verdicts matched the test's. All four nits are addressed at bb6f4ae:

    1. The entry guard's comment was wrong about node -: there argv[1] is -, and code from stdin runs nothing. The comment now says so.
    2. The oracle copies Next's managedPaths by hand, and the verdicts depend on them. A new test reads Next's build/webpack-config.js and fails if that block changes. The pin test's comment now says "could change".
    3. The .ie-hack test couldn't catch a check that didn't ask for optimize when run with NODE_ENV=production. It now stubs NODE_ENV.
    4. This body said a file link fails the test "however it's reached". A file link in a hidden directory, or inside a package under node_modules, doesn't, rightly, because webpack never reads it. The calculator section is reworded.
  • Independent review, round 5 (Opus 5.5, at bb6f4ae) approved, with two nits. First, the new test checks only Next's snapshot block, not options set after it, in either app's webpack hook, or in a copy of Next only the calculator uses. Nothing sets them there today: the review grepped Next 15.5.12's dist and read both hooks. That test hardening goes in a follow-up PR, so this fix ships now. Second, two overstatements in this body, fixed above.

After merge

The next production deploy builds without the cache, so the homepage tool cards and supporter cards get their styles back. I'll check /us and /us/supporters on production. Then #1424 syncs with main and gets a fresh preview build.

axiom: n/a: website and calculator build tooling only

🤖 Generated with Claude Code

policyengine.org's stylesheet is missing twelve Tailwind classes that the
website's source uses, so the homepage tool cards render with no padding,
gap or icon box, and supporter card headers aren't centered. A clean build
of the same source has all of them.

A production build that restored webpack's persistent cache kept CSS
compiled from older source when only server-rendered files had changed:
reproduced locally by adding a class to homeCardClassName.ts and
rebuilding on a warm .next/cache. The client build's cache has no record
of those files. Vercel restores that cache before every build.

Production builds now run without the cache. Dev runs on Turbopack and is
unchanged. The calculator's CSS matches a clean build, so it keeps its
cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
policyengine-calculator-next Ready Ready Preview Oct 26, 2026 4:04am UTC
policyengine-website Ready Ready Preview Oct 26, 2026 4:04am UTC

Request Review

…ild workers, and correct the cause

- scripts/check-built-css.mjs compares the classes in .next/static/css with
  a fresh compile of the app's stylesheet, scanned from the app's directory.
  It fails the build if a class the source uses is missing, or if the build
  has a Tailwind utility the source doesn't use. Both the website's build.sh
  and the calculator's build script run it after next build.
- Against production's live stylesheet it reports the 12 missing classes.
- next.config.ts states the real cause: Tailwind makes src/ a context
  dependency of globals.css, and webpack can't snapshot a directory holding
  symlinks to files (src/data/*.json), so the cached CSS module records no
  inputs and warm builds reuse it whatever changed.
- experimental.webpackBuildWorker keeps the build workers that a webpack hook
  otherwise turns off.
- build.sh drops .next/cache/webpack, which production builds no longer read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…racter as an escape

An attribute value can only hold a dot inside a string or an escape, so the
separate attribute-selector skip did nothing; a mutant removing it survived.
The property test now also covers an escaped dot in an unquoted attribute
value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and harden the selector reader

- The config comment and the script's header say the check compares class names, not values.
- A test reads Tailwind's source directories for each app and asserts the calculator's hold no file symlinks.
- The selector reader skips comments; its property test draws an ASCII-heavy alphabet with :has and two-class wrappers.
- The entry guard resolves process.argv[1], so the script still runs through a symlinked path.
- The failure message says how to recover on Vercel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…den the check's entry guard

- The calculator's cause test takes webpack's own snapshot verdict, with a production build's options and Next's managedPaths, instead of walking for symlinks. A file link behind a directory link now fails it; the website stays the positive control.
- The entry guard compares real paths on both sides and can't throw, so --preserve-symlinks-main and node -e imports behave.
- New selector examples: a tab or form feed after a hex escape, upper-case hex, and a comment followed by *.
- The fresh-compile fixture has a rule Lightning CSS rejects, which pins optimize: true.
- build.sh, the script's header and its failure message say the check compares class names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The webpack oracle copies the managedPaths Next gives webpack, and its
verdicts depend on them. A new test reads Next's build/webpack-config.js
and fails if the block that sets webpack's snapshot paths changes.

The .ie-hack test now sets NODE_ENV to development, since Tailwind
optimizes by default under production and would otherwise hide a check
that didn't ask for it. The entry guard's comment now says what happens
under `node -`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 26, 2026 05:19
@MaxGhenis
MaxGhenis merged commit 87d3359 into main Oct 26, 2026
11 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged at the reviewed head bb6f4ae with --admin --merge --match-head-commit, because the only block was a required approving review that no one but Max could give on his own PR.

Gates at merge:

  • gh pr checks exited 0: 11 of 11 passed at bb6f4ae.
  • mergeable was MERGEABLE, and there was no CHANGES_REQUESTED review.
  • An independent reviewer (Opus 5.5) approved at this exact head, round 5, with two nits. The body nit is fixed above. The other hardens the new test against snapshot options set outside Next's block, which nothing sets in Next 15.5.12; it goes in a follow-up PR.

Earlier rounds: 1 (01815e5, ten changes), 2 (5213962, six), 3 (c029781, five) and 4 (348c40d, approved with four nits, fixed in bb6f4ae). Reviews are kept in ~/reviews/app-v2-pr1425/.

Next: check /us and /us/supporters on production once the deploy lands, and run the CSS check against the live stylesheet.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Production after the merge (87d3359, deploy dpl_BnXf7kM4G3rJ3XAu4aouuXGt1Sa8):

  • Both production builds ran the check and passed: the website "has all 598 classes of a fresh compile of src/app/globals.css, plus 1 from other stylesheets", and the calculator "has all 783 classes".
  • policyengine.org/us now serves f4d325ca88a07d63.css instead of b76c71cfd6868604.css. The check, run against the live file, passes: all 598 classes.
  • At a 1024 px window: the /us tools grid has a 24px gap, the card bodies 32px padding, and the icon boxes are 40px with rgb(230, 255, 250). On /us/supporters, all 14 sm:items-center headers compute align-items: center, and the no-logo spacers show at 200px.

This branch was successfully deployed

2 active deployments
Preview – policyengine-calculator-next — bb6f4ae8 Deployed Oct 26, 2026 by vercel[bot]
Preview – policyengine-website — bb6f4ae8 Deployed Oct 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant