Skip to content

fix(pages): preload initial dev stylesheets#2423

Merged
james-elicx merged 5 commits into
mainfrom
codex/fix-pages-dev-css-fouc
Jun 30, 2026
Merged

fix(pages): preload initial dev stylesheets#2423
james-elicx merged 5 commits into
mainfrom
codex/fix-pages-dev-css-fouc

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • emit Pages Router dev initial stylesheet links from the _app and matched page CSS graph
  • include CSS Modules, static re-exported dependencies, page-level CSS, and source URL/basePath handling
  • align production Pages asset ordering with Next.js by collecting _app assets before page assets

Validation

  • vp check --fix packages/vinext/src/index.ts packages/vinext/src/server/dev-server.ts packages/vinext/src/server/pages-dev-module-url.ts packages/vinext/src/server/pages-page-handler.ts tests/pages-router.test.ts tests/scss.test.ts
  • vp test run tests/pages-router.test.ts -t "dev Pages client assets expose _app global CSS|basePath source URLs|metadata updates|production Pages asset tags include _app stylesheet assets"
  • vp test run tests/scss.test.ts
  • git diff --check

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2423
npm i https://pkg.pr.new/vinext@2423

commit: 14ca908

@github-actions

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(pages): preload initial dev stylesheets

Reviewed the diff, ran the targeted tests (tests/pages-router.test.ts, tests/scss.test.ts), and vp check on all changed source + test files. Everything passes and the core approach is sound: emit blocking <link rel="stylesheet"> tags for the _app + matched-page CSS graph in dev (fixing FOUC), and align prod asset ordering (_app before page) with Next.js document ordering.

No correctness blockers found. A few observations below, mostly maintainability.

Non-blocking observations

1. Duplicated identical regexes (packages/vinext/src/index.ts:374-375)

const STYLESHEET_IMPORT_RE = /\.(?:css|scss|sass|less)$/i;
const STYLESHEET_FILE_RE = /\.(?:css|scss|sass|less)$/i;

These two constants are byte-for-byte identical. Consider collapsing to one to avoid them drifting apart.

2. isPagesAppFile / isPotentialPagesAssetGraphScript defined three times
The same two helpers are defined inline in hotUpdate (index.ts:3598-3620) and again in configureServer (index.ts:3796-3819), and the assets-module invalidation loop is repeated across hotUpdate, the add/change/unlink watcher handlers. Worth hoisting to a single module-scope helper to keep the invalidation rules in one place — easy for these to diverge in a future edit.

3. Possible double full-reload for _app-graph script edits
For a script file in the _app graph, hotUpdate invalidates the assets module and sends { type: "full-reload" } (returning []), while watcher.on("change") also calls invalidatePagesClientAssetsModule(true) which sends another full-reload. Functionally harmless (idempotent reload) but fires the WS message twice per change.

4. Broad invalidation scope
isPotentialPagesAssetGraphScript matches any non-node_modules, non-app/ script under root. In a Pages app this means editing any unrelated .ts/.tsx (e.g. a util in lib/) invalidates the assets module and triggers a full reload. This is intentional to catch transitive _app deps in lib/, and pure Pages apps already full-reload on most script edits, so it's acceptable — just noting the widened scope.

Verified

  • basePath handling: createPagesDevAssetUrl emits root-relative hrefs (/styles/...); the final HTML passes through server.transformIndexHtml (dev-server.ts:2085) which applies Vite's base, so basePath (/docs/...) is correctly prepended. The assetPrefix-should-not-leak test confirms this.
  • Query/type-only imports correctly excluded (ignored.css?raw, type-only.module.css).
  • Manifest key lookup is robust via getManifestFilesForModule's suffix-match fallback even though index.ts keys are normalizePathSeparators'd while dev-server looks up raw route.filePath/appAssetPath.
  • Prod ordering swap (_app first) dedupes correctly in collectAssetTags and all existing asset tests still pass.

@ask-bonk

ask-bonk Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2423.

Summary

I reviewed PR #2423 (fix(pages): preload initial dev stylesheets), ran the targeted tests, and ran vp check on all changed source and test files. Everything passes.

Verdict: No correctness blockers. The approach is sound — emitting blocking <link rel="stylesheet"> tags for the _app + matched-page CSS graph in dev fixes FOUC, and reordering _app assets before page assets matches Next.js document ordering.

Non-blocking observations posted:

  1. Two identical regexes (STYLESHEET_IMPORT_RE / STYLESHEET_FILE_RE) that could be collapsed.
  2. isPagesAppFile / isPotentialPagesAssetGraphScript defined three times across hotUpdate and configureServer — worth hoisting to module scope.
  3. Possible double full-reload WS message for _app-graph script edits (harmless but redundant).
  4. Broad invalidation scope for any non-node_modules/non-app script (intentional, just noted).

Verified working:

  • basePath handling via transformIndexHtml applying Vite's base (assetPrefix correctly does not leak).
  • Query/type-only imports correctly excluded.
  • Manifest key lookup robust via suffix-match fallback.
  • Prod ordering swap dedupes correctly; all existing asset tests pass.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(pages): preload initial dev stylesheets

Re-reviewed with a focus on correctness. The core approach is sound (emit blocking <link rel="stylesheet"> for the _app + matched-page CSS graph in dev, align prod _app-before-page ordering), and the test coverage for basePath/assetPrefix, transitive deps, type-only/query-only exclusion, HMR re-collection, and dev/prod parity is excellent.

I found one issue worth addressing before merge.

Possible blocker — App Router HMR degraded in hybrid (app + pages) apps

packages/vinext/src/index.ts:3596-3636

The hotUpdate guard changed from if (!hasPagesDir || hasAppDir) return; to if (!hasPagesDir) return;. Previously, in any app that has an app/ directory, this hotUpdate hook was a complete no-op and deferred entirely to the RSC plugin's fast-refresh. Now it runs for hybrid apps too.

The problem is isPotentialPagesAssetGraphScript (index.ts:3606-3620): it returns true for any script file under root that is not in node_modules and not literally inside app/. In a hybrid app, shared code like lib/*.ts, components/*.tsx, utils/*.ts is routinely imported by App Router routes. Editing such a file now hits:

if (pagesAppChanged || isPotentialPagesAssetGraphScript(options.file)) {
  options.server.ws.send({ type: "full-reload" });
  return [];   // <-- runs BEFORE the `if (hasAppDir) return;` below
}

Returning [] filters all modules out of the HMR update for downstream plugins, and the explicit full-reload forces a hard page reload. So in a hybrid app, editing a shared util that an App Router page imports now triggers a full page reload instead of React fast-refresh — a regression from main, where the same edit was handled by the RSC plugin's HMR.

The appDir exclusion only excludes files literally under app/; it does not exclude shared code that App Router consumes. The STYLESHEET_FILE_RE branch above is less severe (it only invalidates the assets module without forcing a reload before hasAppDir return), but the script branch returns early.

Suggested fix: gate the script-graph full-reload behind "pages-only" detection, or restrict the broad isPotentialPagesAssetGraphScript reload to apps without an app/ dir. For hybrid apps, scope the asset-module invalidation to actual transitive _app deps (or just invalidate without forcing full-reload/return []) so App Router fast-refresh is preserved. A hybrid HMR test would lock this down — the new tests are all pure-Pages.

Non-blocking

  1. Duplicate identical regexes (index.ts:374-375): STYLESHEET_IMPORT_RE and STYLESHEET_FILE_RE are byte-for-byte identical — collapse to one to avoid drift.

  2. Helpers defined three timesisPagesAppFile / isPotentialPagesAssetGraphScript are defined inline in hotUpdate (index.ts:3598-3620) and again in configureServer (index.ts:3792-3820), with the same invalidation predicate repeated across the add/change/unlink watchers. Hoist to module scope so the (now subtle) reload rules live in one place.

  3. Double full-reload — for an _app-graph script edit, hotUpdate sends full-reload and watcher.on("change") calls invalidatePagesClientAssetsModule(true) which sends another. Idempotent but redundant.

  4. Import attributes skipped (index.ts:469): if (statement.attributes && statement.attributes.length > 0) continue; will skip import "./x.css" with { type: "css" }. Edge case, likely fine, just noting.

@ask-bonk

ask-bonk Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2423.

Summary

I re-reviewed PR #2423 (fix(pages): preload initial dev stylesheets) focusing on correctness. The core approach and test coverage are strong, but I found one issue the prior review treated as merely "intentional broad scope" that is actually a regression for hybrid (App + Pages) apps.

Possible blocker — App Router HMR degraded in hybrid apps (index.ts:3596-3636)

The hotUpdate guard changed from if (!hasPagesDir || hasAppDir) return; to if (!hasPagesDir) return;, so the hook now runs for hybrid apps where it previously deferred entirely to the RSC plugin's fast-refresh. Because isPotentialPagesAssetGraphScript matches any script outside app/ and node_modules, editing shared code (lib/, components/, utils/) that App Router routes import now hits:

options.server.ws.send({ type: "full-reload" });
return [];   // runs BEFORE the `if (hasAppDir) return;` below

forcing a full page reload instead of React fast-refresh — a regression from main. The appDir exclusion only excludes files literally under app/, not shared code App Router consumes. All new tests are pure-Pages, so this path is uncovered.

Suggested fix: scope the script-graph full-reload to pages-only apps (or apps without an app/ dir), and for hybrid apps invalidate the assets module without full-reload/return []. Add a hybrid HMR test.

Non-blocking: duplicate identical regexes (374-375), the same helpers defined three times (hoist to module scope), a redundant double full-reload, and import ... with { type: "css" } being skipped by the attributes filter.

github run

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 14ca908 against base c8aef02 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 124.0 KB 124.0 KB ⚫ +0.0%
Client entry size (gzip) vinext 118.8 KB 118.8 KB ⚫ +0.0%
Dev server cold start vinext 2.53 s 2.54 s ⚫ +0.1%
Production build time vinext 3.03 s 3.05 s ⚫ +0.7%
RSC entry closure size (gzip) vinext 95.2 KB 95.2 KB ⚫ -0.0%
Server bundle size (gzip) vinext 161.5 KB 161.5 KB ⚫ -0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(pages): preload initial dev stylesheets

Re-reviewed after the new ed8885a ("harden dev stylesheet parity") commit. That commit improves several things — ISR cached HTML and custom error pages now get the initial stylesheet head, error-page _app/error-page asset paths are resolved correctly, and .less was dropped from the stylesheet regexes. Good cleanups.

However, the new commit introduces a new correctness regression in addition to leaving a previously-flagged blocker unaddressed.

Blocker 1 (new) — ?raw / ?url / ?inline CSS imports are broken app-wide

packages/vinext/src/index.ts:2970-2973 (resolveId) and :476-481 / :400-402

The commit removed the query/hash guard from isStylesheetSpecifier:

// before (commit 7538189 — correct):
function isStylesheetSpecifier(specifier: string): boolean {
  if (specifier.includes("?") || specifier.includes("#")) return false;
  return STYLESHEET_IMPORT_RE.test(specifier.toLowerCase());
}

// after (commit ed8885a — strips the query before testing):
function isStylesheetSpecifier(specifier: string): boolean {
  return STYLESHEET_IMPORT_RE.test(stripViteModuleQuery(specifier).toLowerCase());
}

and added a new resolveId branch:

const cleanStylesheetId = stripViteModuleQuery(cleanId);
if (cleanStylesheetId !== cleanId && isStylesheetSpecifier(cleanId)) {
  return await this.resolve(cleanStylesheetId, importer, { skipSelf: true });
}

?raw, ?url, and ?inline are standard Vite import query modifiers with distinct, non-stylesheet semantics:

  • import s from "./a.css?raw" → the CSS source as a string
  • import u from "./a.css?url" → the resolved URL string
  • import "./a.css?inline" → inlined, not injected as a <link>

This resolveId branch strips the query and re-resolves a.css?raw to the bare a.css CSS module, so the imported value is now the CSS-module/side-effect form instead of the raw string/URL — ?raw/?url/?inline for CSS are broken. And because this hook is not gated by command, it runs for both dev and production builds and for both App Router and Pages Router (filter \.(?:css|scss|sass)[?#]), so this is app-wide, not Pages-dev-only.

Separately, in collectDevPagesAppStylesheetAssets (:476), isStylesheetSpecifier("./a.css?raw") now returns true, so a ?raw import is added to the dev stylesheet manifest and emitted as a blocking <link rel="stylesheet">.

This is a deliberate behavior reversal between the two commits: in 7538189 the test asserted expect(html).not.toContain("ignored.css") for an import "@/styles/ignored.css?raw" (correctly excluded); in ed8885a that fixture was renamed to query.css?raw / hash.css#hash and the test now expects them emitted as stylesheets (devStylesheetHrefs contains /styles/query.css, /styles/hash.css). The first commit had the correct behavior.

Suggested fix: restore the query/hash exclusion for stylesheet detection (a CSS import carrying ?raw/?url/?inline/#… is not an initial stylesheet), and drop the resolveId query-stripping branch — let Vite resolve *.css?raw etc. with its normal semantics. The ?used/internal Vite CSS query is the only case that needs collapsing, and Vite already handles that; vinext shouldn't intercept user ?raw/?url/?inline. Add a regression test that a ?raw/?url/?inline CSS import is NOT emitted as <link> and still imports the raw/url/inline value.

Blocker 2 (still unaddressed) — App Router HMR degraded in hybrid (app + pages) apps

packages/vinext/src/index.ts:3601 and :3636-3640

This was flagged in the prior review and is unchanged. The hotUpdate guard is still if (!hasPagesDir) return;, and the script-graph branch still runs before if (hasAppDir) return;:

if (pagesAppChanged || isPotentialPagesAssetGraphScript(options.file)) {
  options.server.ws.send({ type: "full-reload" });
  return [];          // line 3638
}
if (hasAppDir) return; // line 3640 — too late

isPotentialPagesAssetGraphScript returns true for any script under root that is not in node_modules and not literally inside app/. In a hybrid app, shared code (lib/*.ts, components/*.tsx, utils/*.ts) imported by App Router routes hits this branch, forcing a hard full-reload instead of React fast-refresh — a regression from main, where this hook was a no-op whenever hasAppDir. The appDir exclusion only excludes files literally under app/, not shared code App Router consumes. All new tests are pure-Pages, so this path stays uncovered.

Suggested fix: scope the script-graph full-reload/return [] to apps without an app/ dir (or to actual transitive _app deps). For hybrid apps, invalidate the assets module without forcing full-reload/return [] so App Router fast-refresh is preserved. Add a hybrid HMR test.

Non-blocking

  1. Duplicate identical regexes (index.ts:374-375): STYLESHEET_IMPORT_RE and STYLESHEET_FILE_RE are still byte-for-byte identical (/\.(?:css|scss|sass)$/i). Collapse to one to avoid drift.

  2. Helpers defined three timesisPagesAppFile / isPotentialPagesAssetGraphScript are still defined inline in hotUpdate and again in configureServer, with the same invalidation predicate repeated across the add/change/unlink watchers. Hoist to module scope so the reload rules live in one place.

  3. Double full-reload — for an _app-graph script edit, hotUpdate sends full-reload and watcher.on("change") also calls invalidatePagesClientAssetsModule(true) which sends another. Idempotent but redundant.

  4. import "./x.css" with { type: "css" } — skipped by the statement.attributes.length > 0 guard (index.ts:468). Edge case, likely fine, just noting.

Verified working

  • ISR cached HTML and custom error pages now carry the initial stylesheet head (dev-server.ts collectDevInitialStylesheetHeadHTML); error-page _app/error asset paths resolved via findFileWithExts.
  • basePath handling via transformIndexHtml applying Vite's base (assetPrefix correctly does not leak).
  • Type-only imports excluded; manifest key lookup robust via suffix-match fallback.
  • Prod _app-before-page ordering swap dedupes correctly; existing asset tests pass.
  • Targeted dev tests pass (which is exactly what codifies the Blocker 1 regression).

@ask-bonk

ask-bonk Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2423.

Summary

I re-reviewed PR #2423 after the new ed8885a ("harden dev stylesheet parity") commit. That commit makes good improvements (ISR + error-page stylesheet heads, correct error-page asset path resolution, dropping .less), but I found two blockers:

Blocker 1 (new this commit) — ?raw / ?url / ?inline CSS imports broken app-wide.
ed8885a removed the query/hash guard from isStylesheetSpecifier and added a resolveId branch (index.ts:2970-2973) that strips the query from any *.css?... import and re-resolves it to the bare CSS module. This breaks standard Vite query semantics: ?raw (source string), ?url (URL string), and ?inline (inlined, no <link>) all now resolve to the CSS-module form, and a ?raw import even gets emitted as a blocking <link rel="stylesheet">. The hook is not gated by command, so it affects both dev and production builds, App and Pages Router. This is a deliberate reversal of the correct behavior in the first commit (7538189), which excluded ?raw imports — the test was changed from not.toContain("ignored.css") to expecting query.css?raw/hash.css#hash as stylesheets, codifying the regression.

Blocker 2 (still unaddressed) — App Router HMR degraded in hybrid apps.
The hotUpdate guard is still if (!hasPagesDir) return;, and the script-graph full-reload/return [] branch (index.ts:3636-3639) runs before if (hasAppDir) return; (3640). Editing shared lib//components/ code that App Router routes import now forces a hard reload instead of fast-refresh in hybrid apps — a regression from main. New tests are all pure-Pages, so it's uncovered.

Non-blocking: duplicate identical regexes (374-375), helpers defined three times, redundant double full-reload, and import ... with { type: "css" } skipped by the attributes guard.

Both blockers come with suggested fixes and recommended regression tests.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(pages): preload initial dev stylesheets

Re-reviewed after the two new commits (3781e8f "preserve hybrid app HMR" and 14ca908 "preserve CSS resource query imports"). Both blockers from my prior review are now fixed, with proper regression tests. I ran the targeted tests and vp check on all changed files — everything passes. No remaining correctness blockers.

Blocker 1 (CSS ?raw/?url/?inline broken app-wide) — RESOLVED in 14ca908

The query/hash guard is restored in isStylesheetSpecifier (index.ts:400-402):

function isStylesheetSpecifier(specifier: string): boolean {
  if (specifier.includes("?") || specifier.includes("#")) return false;
  return STYLESHEET_IMPORT_RE.test(specifier.toLowerCase());
}

and the resolveId query-stripping branch was removed, along with the \.(?:css|scss|sass)[?#] entry from the hook filter — so Vite now resolves *.css?raw/?url/?inline with its normal semantics. The test was corrected to the right behavior: query.css?raw is now asserted not to be emitted as a stylesheet (devStylesheetHrefs no longer contains /styles/query.css, SSR manifest not.toContain("styles/query.css"), and the prod test asserts the ?raw CSS marker is not bundled). This is exactly the fix I suggested.

Blocker 2 (App Router HMR degraded in hybrid apps) — RESOLVED in 3781e8f

The script-graph full-reload/return [] branch is now gated so it does not fire for hybrid apps (index.ts:3631):

if (pagesAppChanged || (!hasAppDir && pagesAssetGraphScriptChanged)) {
  options.server.ws.send({ type: "full-reload" });
  return [];
}
if (hasAppDir) return;

Editing shared lib//components/ code that App Router routes import no longer forces a hard reload in hybrid apps — it falls through to if (hasAppDir) return; and defers to RSC fast-refresh. The asset-module invalidation (no forced reload) still runs so Pages stylesheet metadata stays fresh. The same !hasAppDir && pagesAssetGraphScriptChanged gating was applied consistently to the add/change/unlink watchers. A focused hybrid HMR test was added ("does not force full reload for shared App Router code in hybrid apps") asserting hotUpdateResult is undefined and no full-reload is sent — it passes.

Verified working

  • Targeted dev/prod stylesheet tests pass, including the corrected ?raw/query exclusion and the new hybrid HMR test.
  • ISR cached HTML and custom error pages carry the initial stylesheet head; _app/error asset paths resolved via findFileWithExts.
  • basePath handling via transformIndexHtml applying Vite's base (assetPrefix correctly does not leak); hrefs escaped via escapeHtmlAttr, nonce applied.
  • _app-before-page asset ordering preserved ([appAssetPath, route.filePath]).
  • vp check clean on all 6 changed files.

Non-blocking (carried over, low priority)

  1. Duplicate identical regexes (index.ts:374-375): STYLESHEET_IMPORT_RE and STYLESHEET_FILE_RE are still byte-for-byte identical (/\.(?:css|scss|sass)$/i). Collapse to one to avoid drift.
  2. Helpers defined twiceisPagesAppFile / isPotentialPagesAssetGraphScript are defined inline in hotUpdate and again in configureServer. Hoist to module scope so the (now subtle) reload rules live in one place; this is also what would have made the two-commit fix a one-line change.
  3. import "./x.css" with { type: "css" } — skipped by the statement.attributes.length > 0 guard (index.ts:469). Edge case, likely fine, just noting.

Nice work addressing both blockers cleanly. LGTM once you optionally fold in the non-blocking cleanups.

@ask-bonk

ask-bonk Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2423.

Summary

I re-reviewed PR #2423 after the two newest commits (3781e8f and 14ca908). Both blockers I flagged in prior reviews are now fixed, with regression tests. I ran the targeted tests and vp check — all pass. No remaining correctness blockers.

Blocker 1 (CSS ?raw/?url/?inline broken app-wide) — RESOLVED in 14ca908

  • Restored the query/hash guard in isStylesheetSpecifier (index.ts:400-402)
  • Removed the resolveId query-stripping branch and the \.(?:css|scss|sass)[?#] filter entry, so Vite resolves *.css?raw etc. with normal semantics
  • Test corrected: query.css?raw is now asserted not to be emitted as a stylesheet, plus a prod assertion that the ?raw CSS is not bundled

Blocker 2 (App Router HMR degraded in hybrid apps) — RESOLVED in 3781e8f

  • The script-graph full-reload/return [] branch is now gated if (pagesAppChanged || (!hasAppDir && pagesAssetGraphScriptChanged)) (index.ts:3631), so editing shared lib//components/ code in hybrid apps falls through to RSC fast-refresh instead of forcing a hard reload
  • Same gating applied to the add/change/unlink watchers
  • A focused hybrid HMR regression test was added and passes

Non-blocking (carried over): duplicate identical regexes (374-375), the two helpers defined twice (hoist to module scope), and import ... with { type: "css" } skipped by the attributes guard.

Clean fixes for both blockers — LGTM.

github run

@james-elicx james-elicx marked this pull request as ready for review June 30, 2026 09:10
@james-elicx james-elicx merged commit f4a96c9 into main Jun 30, 2026
60 checks passed
@james-elicx james-elicx deleted the codex/fix-pages-dev-css-fouc branch June 30, 2026 09:10
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