diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4811d6269..e2f0601c3 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -74,7 +74,10 @@ jobs: # the versioned service-worker stamp AND the static i18n catalog # assets (public/i18n/.js) the client boots from — without # them every UI interaction waits on missing catalogs. - - run: pnpm build + # The repository-wide Next type worker exceeds V8's ~4 GiB default + # old-space ceiling. Keep the larger heap scoped to this production + # build only; tests and the runtime retain their normal limits. + - run: NODE_OPTIONS=--max-old-space-size=8192 pnpm build # Client-bundle budget gate — rides this job because it is the one # place CI already has a production build on disk. Catches the diff --git a/Dockerfile b/Dockerfile index dca41b4ed..5eeb810ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,10 @@ RUN pnpm db:generate # Build Next.js ENV NEXT_TELEMETRY_DISABLED=1 -RUN pnpm build +# Keep the larger V8 heap build-only. Next's repository-wide TypeScript +# worker exceeds the default ~4 GiB old-space ceiling; the runner stage below +# starts fresh and therefore does not inherit this setting. +RUN NODE_OPTIONS=--max-old-space-size=8192 pnpm build # ── Stage 3: Production runner ───────────────────────────── FROM node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2 AS runner diff --git a/bundle-budget.json b/bundle-budget.json index 8a82ab4b4..95a5fb7eb 100644 --- a/bundle-budget.json +++ b/bundle-budget.json @@ -1,11 +1,11 @@ { - "$comment": "Client-bundle budgets, enforced by scripts/check-bundle-budget.mjs (--check) after `next build` in CI. Values are KB gzip and include the shared rootMainFiles baseline. Re-measured at v1.34.0: / 430, /insights 417, /measurements 417, /insights/mood 435, total 3130, baseline 130. The aggregate rose 70 KB from v1.33.2's 3060 for the same reason the v1.33.0 raise did: new surfaces, not a duplicated library. This release adds the reference-composite health-score card (per-pillar detail cards, method notes), the structured anamnesis facts editor, custom-metric correlation opt-in, and pattern-dismissal controls on two card types. Every per-route cap still passes with 23-30 KB headroom, and the Recharts chunk count is still 1 and structurally guarded. Raised 3120 to 3140 for that reason, not to make a red gate pass — the per-route budgets are the real ceiling on any single page; the aggregate exists to catch exactly the duplicated-library case that did not happen here.", + "$comment": "Client-bundle budgets, enforced by scripts/check-bundle-budget.mjs (--check) after `next build` in CI. Values are KB gzip and include every emitted lazy route and locale chunk, while route budgets include the shared rootMainFiles baseline. Re-measured at v1.34.1: / 431, /insights 420, /measurements 417, /insights/mood 438, total 3238, baseline 130, lazy catalogs 792. The 108 KB aggregate rise from v1.34.0's 3130 is not one user load: the explicit assessment error/timeout/poll-exhaustion contract adds 2.7-4.4 KB to each of roughly forty mutually exclusive metric route chunks, which the whole-build sum counts independently. Catalogs account for only about 2 KB of the delta, no catalog is statically route-referenced, and Recharts remains exactly one fingerprinted chunk. A measured single next/dynamic assessment boundary was reverted because it raised the artifact further to 3243 KB while saving only about 1 KB per route. The aggregate cap therefore moves narrowly from 3140 to 3260 (22 KB headroom); per-route ceilings, the static-catalog guard, and the one-Recharts-chunk guard remain unchanged.", "routesKbGz": { "/page": 460, "/insights/page": 445, "/measurements/page": 445, "/insights/mood/page": 460 }, - "totalClientKbGz": 3140, + "totalClientKbGz": 3260, "maxRechartsChunks": 1 } diff --git a/scripts/check-bundle-budget.mjs b/scripts/check-bundle-budget.mjs index d38c17174..cd2518981 100644 --- a/scripts/check-bundle-budget.mjs +++ b/scripts/check-bundle-budget.mjs @@ -133,12 +133,18 @@ for (const { route, file } of manifests) { const chunkDir = join(BUILD_DIR, "static", "chunks"); let totalGz = 0; let rechartsChunks = 0; +let catalogChunks = 0; +let catalogGz = 0; let largest = { file: "-", gz: 0 }; for (const f of readdirSync(chunkDir)) { if (!f.endsWith(".js")) continue; const info = chunkInfo(join("static", "chunks", f)); totalGz += info.gz; if (info.recharts) rechartsChunks += 1; + if (info.catalog) { + catalogChunks += 1; + catalogGz += info.gz; + } if (info.gz > largest.gz) largest = { file: f, gz: info.gz }; } const rootCatalogRef = rootFiles.some((f) => chunkInfo(f).catalog); @@ -150,6 +156,10 @@ const budget = existsSync(BUDGET_PATH) console.log(`shared baseline (rootMainFiles): ${fmt(rootGz)} gz`); console.log(`all client chunks: ${fmt(totalGz)} gz`); +console.log( + ` lazy message catalogs: ${fmt(catalogGz)} gz (${catalogChunks} chunks)`, +); +console.log(` other client chunks: ${fmt(totalGz - catalogGz)} gz`); console.log( `largest chunk: ${fmt(largest.gz)} gz ${largest.file}`, );