From a324e067d2055fa3e32dd7a039c5e66a62bef3b9 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:46:46 +0800 Subject: [PATCH 1/6] perf(css): reduce render-blocking production styles --- .../66f364c0-5612-449b-9046-94b4dab0b152.json | 12 ++++++++ .../f4b9c833-03f4-40bd-9537-90501c1287c4.json | 10 +++++++ src/app/globals.css | 5 ++++ src/app/layout.tsx | 6 +++- src/app/mockups/layout.tsx | 1 + src/app/mockups/mockups.css | 9 ++++++ tests/css-delivery-contract.test.ts | 29 +++++++++++++++++++ 7 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 docs/outstanding-issues-inbox/66f364c0-5612-449b-9046-94b4dab0b152.json create mode 100644 docs/outstanding-issues-inbox/f4b9c833-03f4-40bd-9537-90501c1287c4.json create mode 100644 src/app/mockups/mockups.css create mode 100644 tests/css-delivery-contract.test.ts diff --git a/docs/outstanding-issues-inbox/66f364c0-5612-449b-9046-94b4dab0b152.json b/docs/outstanding-issues-inbox/66f364c0-5612-449b-9046-94b4dab0b152.json new file mode 100644 index 0000000000..37f51f084b --- /dev/null +++ b/docs/outstanding-issues-inbox/66f364c0-5612-449b-9046-94b4dab0b152.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "id": "66f364c0-5612-449b-9046-94b4dab0b152", + "createdOn": "2026-08-13", + "action": "update", + "payload": { + "id": "#117", + "summary": "All live mobile routes breach LCP; shared render-blocking CSS and font are the current bottleneck", + "detail": "PR #1915 is merged and deployed at exact Railway SHA ca788d41e1e6b64dc6b9bc63609074d92470d7e7. Three-sample live medians on that SHA are Documents 3611 ms, DSM 3600 ms, Forms 3715 ms, root 3948 ms, Therapy 3543 ms, and Services 3791 ms; desktop LCP is 584-691 ms and CLS is within the mobile rule. Trace attribution shows LCP equals FCP, TTFB is only 267-316 ms, and the shared render-blocking stylesheet plus preloaded 28 KB Geist font dominate cold mobile paint. The follow-up branch removes mockup-only Tailwind vocabulary from production CSS (367,050 to 302,113 raw bytes; 55,140 to 46,152 gzip) while retaining a complete route-only mockup sheet, and stops preloading the display-swap body font. Next: merge/deploy that exact SHA, rerun the complete live matrix, then attribute remaining shared JS/hydration cost if any route still exceeds 2500 ms. Therapy field safety review remains required for search/pathways. INP remains unverified because Lighthouse does not measure it and no usable CrUX result exists. Stop: do not strip clinical fields, weaken the Lighthouse budget, or claim an INP pass.", + "source": "PR #1915; live Web Vitals runs 31704500966 and 31704504389; codex/performance-css-delivery" + } +} diff --git a/docs/outstanding-issues-inbox/f4b9c833-03f4-40bd-9537-90501c1287c4.json b/docs/outstanding-issues-inbox/f4b9c833-03f4-40bd-9537-90501c1287c4.json new file mode 100644 index 0000000000..fd49b9f967 --- /dev/null +++ b/docs/outstanding-issues-inbox/f4b9c833-03f4-40bd-9537-90501c1287c4.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "id": "f4b9c833-03f4-40bd-9537-90501c1287c4", + "createdOn": "2026-08-13", + "action": "cancel", + "payload": { + "requestId": "67f9ce93-caf5-4cd4-89e1-742e61ce882a", + "reason": "Superseded by exact deployed-SHA live measurements and the shared CSS/font remediation." + } +} diff --git a/src/app/globals.css b/src/app/globals.css index 346265a373..e2e1b64419 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,4 +1,9 @@ @import "tailwindcss"; +/* Design-scratch routes have their own route-scoped utility sheet. Keeping + their rapidly growing utility vocabulary out of every production page cuts + the shared render-blocking stylesheet without changing mockup rendering. */ +@source not "./mockups"; +@source not "../components/**/*mockup*"; /* Opt-in v2 design-system token layer. Every rule is scoped to `.ckb-v2`, so importing it cannot change any surface that does not carry the class. */ @import "./ckb-v2-tokens.css"; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 817658f0be..c2120dccd3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -38,6 +38,10 @@ const geistSans = localFont({ src: "../../node_modules/next/dist/next-devtools/server/font/geist-latin.woff2", variable: "--font-geist-sans", display: "swap", + // Cold mobile traces show this 28 KB preload competing with the global CSS + // before first paint. The generated fallback is size-adjusted, so paint with + // it immediately and let Geist load on demand without blocking the stylesheet. + preload: false, }); const geistMono = localFont({ @@ -47,7 +51,7 @@ const geistMono = localFont({ // The mono face is only used deep in the UI (tabular figures, `kbd`, code) and // never in initial/LCP text, so don't preload it on every route — it competes // for the critical-path connection. It still loads on-demand via `swap` when - // first painted. The sans face keeps the default preload. + // first painted, matching the sans face's cold-load policy above. preload: false, }); diff --git a/src/app/mockups/layout.tsx b/src/app/mockups/layout.tsx index ec21ee5a5b..18e038c550 100644 --- a/src/app/mockups/layout.tsx +++ b/src/app/mockups/layout.tsx @@ -5,6 +5,7 @@ import type { ReactNode } from "react"; import { mockupsEnabled } from "@/lib/env"; import { MockupsLayoutClient } from "./mockups-layout-client"; +import "./mockups.css"; // Design-exploration prototypes: shipped for shareability, but never indexed // (belt-and-braces alongside the robots.ts /mockups/ disallow). diff --git a/src/app/mockups/mockups.css b/src/app/mockups/mockups.css new file mode 100644 index 0000000000..7416b7c7e5 --- /dev/null +++ b/src/app/mockups/mockups.css @@ -0,0 +1,9 @@ +@import "tailwindcss/utilities.css" layer(utilities) source(none) important; +@reference "../globals.css"; + +/* Re-emit the complete utility vocabulary for mockup routes. This preserves + Tailwind's responsive ordering when a mockup shares shell components with + production. `important` keeps the route sheet authoritative regardless of + whether Next orders it before or after the shared production stylesheet. */ +@source "../"; +@source "../../components"; diff --git a/tests/css-delivery-contract.test.ts b/tests/css-delivery-contract.test.ts new file mode 100644 index 0000000000..5e57fd3eb0 --- /dev/null +++ b/tests/css-delivery-contract.test.ts @@ -0,0 +1,29 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; + +const read = (path: string) => readFileSync(resolve(process.cwd(), path), "utf8"); + +describe("cold-load CSS and font delivery", () => { + it("keeps mockup-only Tailwind utilities out of the production global sheet", () => { + const globals = read("src/app/globals.css"); + const mockups = read("src/app/mockups/mockups.css"); + const layout = read("src/app/mockups/layout.tsx"); + + expect(globals).toContain('@source not "./mockups";'); + expect(globals).toContain('@source not "../components/**/*mockup*";'); + expect(mockups).toContain('@import "tailwindcss/utilities.css" layer(utilities) source(none) important;'); + expect(mockups).toContain('@reference "../globals.css";'); + expect(mockups).toContain('@source "../";'); + expect(mockups).toContain('@source "../../components";'); + expect(layout).toContain('import "./mockups.css";'); + }); + + it("does not preload the display-swap body font ahead of render-blocking CSS", () => { + const rootLayout = read("src/app/layout.tsx"); + const geistSans = rootLayout.slice(rootLayout.indexOf("const geistSans"), rootLayout.indexOf("const geistMono")); + + expect(geistSans).toContain('display: "swap"'); + expect(geistSans).toContain("preload: false"); + }); +}); From 14272ad40868c9e56fe7165dc1de183b72713700 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:47:12 +0800 Subject: [PATCH 2/6] docs(review): record CSS delivery verification --- ...f279d074bdf810c92f2cb5a090b3bd4b521a833f0baa002e0e3.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/2c6a919bc949df279d074bdf810c92f2cb5a090b3bd4b521a833f0baa002e0e3.record.md diff --git a/docs/branch-review-records/2c6a919bc949df279d074bdf810c92f2cb5a090b3bd4b521a833f0baa002e0e3.record.md b/docs/branch-review-records/2c6a919bc949df279d074bdf810c92f2cb5a090b3bd4b521a833f0baa002e0e3.record.md new file mode 100644 index 0000000000..291aa1803d --- /dev/null +++ b/docs/branch-review-records/2c6a919bc949df279d074bdf810c92f2cb5a090b3bd4b521a833f0baa002e0e3.record.md @@ -0,0 +1 @@ +| 2026-08-13 | codex/performance-css-delivery | a324e067d2055fa3e32dd7a039c5e66a62bef3b9 | cold mobile CSS and font delivery | No unresolved findings; review added theme-aware responsive mockup utilities and corrected stale font commentary | build passed (1712 pages); CSS 302113 raw/46203 gzip; contract 2/2; production style 9/9; mockup 15/15; format/issues/ledger passed | From e9ee071a0d36b2d4636b8145a04aea51efc83cd3 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:01:26 +0800 Subject: [PATCH 3/6] docs(review): record synchronized performance head --- ...fc0aef79a3be4d3c206f04207ac0e1b2d8696bb3f657b7df90a.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/e9ef965f3b9bffc0aef79a3be4d3c206f04207ac0e1b2d8696bb3f657b7df90a.record.md diff --git a/docs/branch-review-records/e9ef965f3b9bffc0aef79a3be4d3c206f04207ac0e1b2d8696bb3f657b7df90a.record.md b/docs/branch-review-records/e9ef965f3b9bffc0aef79a3be4d3c206f04207ac0e1b2d8696bb3f657b7df90a.record.md new file mode 100644 index 0000000000..c573ff657c --- /dev/null +++ b/docs/branch-review-records/e9ef965f3b9bffc0aef79a3be4d3c206f04207ac0e1b2d8696bb3f657b7df90a.record.md @@ -0,0 +1 @@ +| 2026-08-13 | codex/performance-css-delivery | aa935df8fa13245af948c4574d791fe31f270d03 | cold mobile CSS and font delivery | No unresolved findings after clean current-main sync; shared header changes pass both production and mockup journeys | production style 9/9; mockup 15/15; contract rerun coordinator-blocked after prior 2/2 pass; no changed-path overlap | From 2c32bcd489eae22ef24e9610a386cd7c7c11490a Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:07:21 +0800 Subject: [PATCH 4/6] fix(css): preserve live themes after mockup navigation --- src/app/mockups/mockups.css | 6 +++--- tests/css-delivery-contract.test.ts | 3 ++- tests/ui-tools-search-mode-mockup.spec.ts | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/app/mockups/mockups.css b/src/app/mockups/mockups.css index 7416b7c7e5..7647cdbbff 100644 --- a/src/app/mockups/mockups.css +++ b/src/app/mockups/mockups.css @@ -1,9 +1,9 @@ -@import "tailwindcss/utilities.css" layer(utilities) source(none) important; +@import "tailwindcss/utilities.css" layer(utilities) source(none); @reference "../globals.css"; /* Re-emit the complete utility vocabulary for mockup routes. This preserves Tailwind's responsive ordering when a mockup shares shell components with - production. `important` keeps the route sheet authoritative regardless of - whether Next orders it before or after the shared production stylesheet. */ + production. The complete theme-aware sheet retains Tailwind's own base and + responsive ordering without overriding inline theming on later live routes. */ @source "../"; @source "../../components"; diff --git a/tests/css-delivery-contract.test.ts b/tests/css-delivery-contract.test.ts index 5e57fd3eb0..87347773bf 100644 --- a/tests/css-delivery-contract.test.ts +++ b/tests/css-delivery-contract.test.ts @@ -12,7 +12,8 @@ describe("cold-load CSS and font delivery", () => { expect(globals).toContain('@source not "./mockups";'); expect(globals).toContain('@source not "../components/**/*mockup*";'); - expect(mockups).toContain('@import "tailwindcss/utilities.css" layer(utilities) source(none) important;'); + expect(mockups).toContain('@import "tailwindcss/utilities.css" layer(utilities) source(none);'); + expect(mockups).not.toMatch(/source\(none\)\s+important/); expect(mockups).toContain('@reference "../globals.css";'); expect(mockups).toContain('@source "../";'); expect(mockups).toContain('@source "../../components";'); diff --git a/tests/ui-tools-search-mode-mockup.spec.ts b/tests/ui-tools-search-mode-mockup.spec.ts index f2c5cc8dd3..f1870b2a93 100644 --- a/tests/ui-tools-search-mode-mockup.spec.ts +++ b/tests/ui-tools-search-mode-mockup.spec.ts @@ -75,6 +75,23 @@ test.describe("Perfected Tools results mode mockup @mockup", () => { await expectNoHorizontalOverflow(page); }); + test("does not let persisted mockup utilities override live-route inline themes", async ({ page }) => { + const mockup = await gotoMockup(page, 1440); + + await mockup.getByRole("link", { name: "Colour coding reference" }).click(); + await expect(page).toHaveURL(/\/reference\/colour-coding$/); + await expect(page.getByRole("heading", { level: 1, name: "Colour coding reference" })).toBeVisible(); + + const inlineBorderColor = await page.evaluate(() => { + const themedCard = document.createElement("div"); + themedCard.className = "border border-[color:var(--border)]"; + themedCard.style.borderTopColor = "rgb(1, 2, 3)"; + document.body.append(themedCard); + return window.getComputedStyle(themedCard).borderTopColor; + }); + expect(inlineBorderColor).toBe("rgb(1, 2, 3)"); + }); + test("desktop details use inline semantics and preserve visible programmatic focus", async ({ page }) => { const mockup = await gotoMockup(page, 1440); const details = mockup.getByRole("button", { name: "View details for Differentials" }); From 4fe764de43ee05880bd850e2e45fbfd215202751 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:31:49 +0800 Subject: [PATCH 5/6] fix(performance): restore LCP font preload --- src/app/layout.tsx | 6 +----- tests/css-delivery-contract.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c2120dccd3..0c8308d27b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -38,10 +38,6 @@ const geistSans = localFont({ src: "../../node_modules/next/dist/next-devtools/server/font/geist-latin.woff2", variable: "--font-geist-sans", display: "swap", - // Cold mobile traces show this 28 KB preload competing with the global CSS - // before first paint. The generated fallback is size-adjusted, so paint with - // it immediately and let Geist load on demand without blocking the stylesheet. - preload: false, }); const geistMono = localFont({ @@ -51,7 +47,7 @@ const geistMono = localFont({ // The mono face is only used deep in the UI (tabular figures, `kbd`, code) and // never in initial/LCP text, so don't preload it on every route — it competes // for the critical-path connection. It still loads on-demand via `swap` when - // first painted, matching the sans face's cold-load policy above. + // first painted, while the body sans face remains preloaded for LCP text. preload: false, }); diff --git a/tests/css-delivery-contract.test.ts b/tests/css-delivery-contract.test.ts index 87347773bf..033ef023fc 100644 --- a/tests/css-delivery-contract.test.ts +++ b/tests/css-delivery-contract.test.ts @@ -20,11 +20,11 @@ describe("cold-load CSS and font delivery", () => { expect(layout).toContain('import "./mockups.css";'); }); - it("does not preload the display-swap body font ahead of render-blocking CSS", () => { + it("keeps the display-swap body font preloaded for LCP text", () => { const rootLayout = read("src/app/layout.tsx"); const geistSans = rootLayout.slice(rootLayout.indexOf("const geistSans"), rootLayout.indexOf("const geistMono")); expect(geistSans).toContain('display: "swap"'); - expect(geistSans).toContain("preload: false"); + expect(geistSans).not.toContain("preload: false"); }); }); From 07520924e639127e036fa3206dc74a402b98e1e8 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:46:58 +0800 Subject: [PATCH 6/6] docs(performance): align LCP font tracking --- .../66f364c0-5612-449b-9046-94b4dab0b152.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/outstanding-issues-inbox/66f364c0-5612-449b-9046-94b4dab0b152.json b/docs/outstanding-issues-inbox/66f364c0-5612-449b-9046-94b4dab0b152.json index 37f51f084b..183374266b 100644 --- a/docs/outstanding-issues-inbox/66f364c0-5612-449b-9046-94b4dab0b152.json +++ b/docs/outstanding-issues-inbox/66f364c0-5612-449b-9046-94b4dab0b152.json @@ -6,7 +6,7 @@ "payload": { "id": "#117", "summary": "All live mobile routes breach LCP; shared render-blocking CSS and font are the current bottleneck", - "detail": "PR #1915 is merged and deployed at exact Railway SHA ca788d41e1e6b64dc6b9bc63609074d92470d7e7. Three-sample live medians on that SHA are Documents 3611 ms, DSM 3600 ms, Forms 3715 ms, root 3948 ms, Therapy 3543 ms, and Services 3791 ms; desktop LCP is 584-691 ms and CLS is within the mobile rule. Trace attribution shows LCP equals FCP, TTFB is only 267-316 ms, and the shared render-blocking stylesheet plus preloaded 28 KB Geist font dominate cold mobile paint. The follow-up branch removes mockup-only Tailwind vocabulary from production CSS (367,050 to 302,113 raw bytes; 55,140 to 46,152 gzip) while retaining a complete route-only mockup sheet, and stops preloading the display-swap body font. Next: merge/deploy that exact SHA, rerun the complete live matrix, then attribute remaining shared JS/hydration cost if any route still exceeds 2500 ms. Therapy field safety review remains required for search/pathways. INP remains unverified because Lighthouse does not measure it and no usable CrUX result exists. Stop: do not strip clinical fields, weaken the Lighthouse budget, or claim an INP pass.", + "detail": "PR #1915 is merged and deployed at exact Railway SHA ca788d41e1e6b64dc6b9bc63609074d92470d7e7. Three-sample live medians on that SHA are Documents 3611 ms, DSM 3600 ms, Forms 3715 ms, root 3948 ms, Therapy 3543 ms, and Services 3791 ms; desktop LCP is 584-691 ms and CLS is within the mobile rule. Trace attribution shows LCP equals FCP, TTFB is only 267-316 ms, and the shared render-blocking stylesheet plus preloaded 28 KB Geist font dominate cold mobile paint. The follow-up branch removes mockup-only Tailwind vocabulary from production CSS (367,050 to 302,113 raw bytes; 55,140 to 46,152 gzip) while retaining a complete route-only mockup sheet. It keeps the display-swap body font preloaded because two hosted Lighthouse runs showed that removing it delayed text LCP, while the non-LCP mono font remains on-demand. Next: merge/deploy that exact SHA, rerun the complete live matrix, then attribute remaining shared JS/hydration cost if any route still exceeds 2500 ms. Therapy field safety review remains required for search/pathways. INP remains unverified because Lighthouse does not measure it and no usable CrUX result exists. Stop: do not strip clinical fields, weaken the Lighthouse budget, or claim an INP pass.", "source": "PR #1915; live Web Vitals runs 31704500966 and 31704504389; codex/performance-css-delivery" } }