From 61b58ae9a3ad40deb6a0818fc6574db91daf4e12 Mon Sep 17 00:00:00 2001
From: Amruth Pillai
Date: Sun, 6 Sep 2026 05:15:24 +0200
Subject: [PATCH 1/4] test: capture offline font network paths
---
.orchestration/plan-27a-diagnostic.md | 157 +++++++++++++++++++
tests/e2e/fixtures/offline-fonts.ts | 67 ++++++++
tests/e2e/specs/offline-fonts.spec.ts | 213 ++++++++++++++++++++++++++
turbo.json | 4 +-
4 files changed, 440 insertions(+), 1 deletion(-)
create mode 100644 .orchestration/plan-27a-diagnostic.md
create mode 100644 tests/e2e/fixtures/offline-fonts.ts
create mode 100644 tests/e2e/specs/offline-fonts.spec.ts
diff --git a/.orchestration/plan-27a-diagnostic.md b/.orchestration/plan-27a-diagnostic.md
new file mode 100644
index 000000000..b3b9e869c
--- /dev/null
+++ b/.orchestration/plan-27a-diagnostic.md
@@ -0,0 +1,157 @@
+# Plan 27 Phase A diagnostic evidence
+
+Date: 2026-09-06
+Issue: [#3377](https://github.com/amruthpillai/reactive-resume/issues/3377)
+Revision: `2a4a1583b` (`origin/main` at run start)
+Scope: Phase A, steps 1–2 only. No resolver, runtime behavior, or remote-source behavior changed.
+
+## Drift and authority
+
+- Worktree started clean and `HEAD` matched `origin/main`; `git diff origin/main...HEAD` was empty.
+- Current catalog is `packages/fonts/src/webfontlist.json`. Its web font records point at both Google Fonts static assets and jsDelivr assets; “Google blocked” is not an offline proof.
+- Browser preview is `apps/web/src/components/typography/font-display.tsx` and calls `FontFace.load()` against each catalog preview URL.
+- Browser PDF preview/download is `apps/web/src/features/resume/export/pdf-document.tsx` → `@reactive-resume/pdf/browser`; registration is `packages/pdf/src/hooks/use-register-fonts.ts`.
+- Server PDF is `apps/server/src/http/resume-pdf.ts` → `createResumePdfDownload`; Playwright browser routing cannot observe that process’s outbound font fetches.
+- The issue is open and unmodified. PR #3455 is the approved planning PR; its plan/decision log grants execution of this bounded diagnostic and manifest evidence.
+
+## Deterministic fixture
+
+`tests/e2e/fixtures/offline-fonts.ts` seeds one disposable resume after sample creation. It writes the same text into basics and summary, hides the picture, selects IBM Plex Serif 400/700 for body and heading, and marks the row public for the server-PDF surface.
+
+The exact markers are versioned as `offline-font-scripts-v1`:
+
+| Marker | Script or coverage |
+| --- | --- |
+| `Latin punctuation • — “quotes” €` | Latin plus General Punctuation and currency |
+| `简体中文` | Han / Simplified Chinese |
+| `العربية` | Arabic |
+| `עברית` | Hebrew |
+| `ไทย` | Thai |
+| `Emoji 🚀` | Emoji |
+
+`tests/e2e/specs/offline-fonts.spec.ts` is opt-in (`OFFLINE_FONT_DIAGNOSTIC=1`) so the normal PR E2E suite does not become network-dependent. Each surface creates a new browser context with persisted auth state, disabled service workers, and no prior browser cache. Every non-same-origin request is aborted and recorded as `{ hostname, path }`; query strings, fragments, headers, bodies, tokens, and full URLs never enter diagnostic output. Reports are attached as JSON and emitted with the same sanitized shape.
+
+The four surfaces are separate tests:
+
+1. Font picker preview opens Typography → Font Family and waits for lazy `FontFace` preview loads.
+2. Builder PDF preview navigates to the builder and records whether a PDF canvas appears.
+3. Browser PDF download uses the Export dialog and extracts marker presence from the downloaded PDF when generation succeeds.
+4. Server PDF calls the public PDF endpoint and extracts marker presence from its response when generation succeeds.
+
+PDF marker results classify missing text as a glyph result (`true`/`false` per marker), while blocked browser font requests classify the browser surface as `network-error`. The server report deliberately says `server-outbound-requests-unobservable-from-playwright`; it does not claim zero server requests.
+
+## Run protocol and cold-cache boundary
+
+Build and database setup follow `tests/e2e/README.md`. Run each surface in a separately restarted production server process so module-level PDF font registration state cannot leak between controls:
+
+```text
+OFFLINE_FONT_DIAGNOSTIC=1 OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED=1 \
+ pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --grep "picker preview"
+```
+
+Stop and restart the production server before repeating the command with `builder PDF`, `browser PDF`, and `server PDF` grep patterns. The environment used for this change had no built `apps/server/dist` or `apps/web/dist`, no running PostgreSQL instance, and no production server to restart, so the cold E2E matrix was not run. This is an explicit infrastructure blocker, not a pass claim. The test still preserves the restart confirmation in its report (`serverRestartConfirmed`) for a later run.
+
+The current Playwright route guard cannot impose host-level egress denial on Node.js running the server. A genuinely cold server test therefore needs a separately restarted server plus host-level egress capture/deny (for example, a controlled network namespace or an approved outbound proxy). Do not infer server network behavior from an empty browser request list.
+
+## Administrator-hosted manifest proposal
+
+This is a proposal, not an asset download. It intentionally contains only the primary family and glyph fallbacks required by the fixture and current PDF fallback map, not the full catalog.
+
+```json
+{
+ "schemaVersion": "offline-fonts-v1",
+ "mode": "local-only",
+ "assetRoot": "/fonts/offline/v1",
+ "families": {
+ "IBM Plex Serif": {
+ "normal": { "400": "ibm-plex-serif/400.ttf", "700": "ibm-plex-serif/700.ttf" },
+ "italic": { "400": "ibm-plex-serif/400-italic.ttf", "700": "ibm-plex-serif/700-italic.ttf" },
+ "preview": "ibm-plex-serif/preview.ttf"
+ },
+ "IBM Plex Sans": {
+ "normal": { "400": "ibm-plex-sans/400.ttf", "700": "ibm-plex-sans/700.ttf" },
+ "italic": { "400": "ibm-plex-sans/400-italic.ttf", "700": "ibm-plex-sans/700-italic.ttf" },
+ "preview": "ibm-plex-sans/preview.ttf"
+ },
+ "Noto Serif": {
+ "normal": { "400": "noto-serif/400.ttf", "700": "noto-serif/700.ttf" },
+ "italic": { "400": "noto-serif/400-italic.ttf", "700": "noto-serif/700-italic.ttf" },
+ "preview": "noto-serif/preview.ttf"
+ },
+ "Noto Sans": {
+ "normal": { "400": "noto-sans/400.ttf", "700": "noto-sans/700.ttf" },
+ "italic": { "400": "noto-sans/400-italic.ttf", "700": "noto-sans/700-italic.ttf" },
+ "preview": "noto-sans/preview.ttf"
+ },
+ "Noto Sans SC": { "normal": { "400": "noto-sans-sc/400.ttf", "700": "noto-sans-sc/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-sc/preview.ttf" },
+ "Noto Serif SC": { "normal": { "400": "noto-serif-sc/400.ttf", "700": "noto-serif-sc/700.ttf" }, "italic": "reuse-normal", "preview": "noto-serif-sc/preview.ttf" },
+ "Noto Sans TC": { "normal": { "400": "noto-sans-tc/400.ttf", "700": "noto-sans-tc/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-tc/preview.ttf" },
+ "Noto Serif TC": { "normal": { "400": "noto-serif-tc/400.ttf", "700": "noto-serif-tc/700.ttf" }, "italic": "reuse-normal", "preview": "noto-serif-tc/preview.ttf" },
+ "Noto Sans JP": { "normal": { "400": "noto-sans-jp/400.ttf", "700": "noto-sans-jp/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-jp/preview.ttf" },
+ "Noto Serif JP": { "normal": { "400": "noto-serif-jp/400.ttf", "700": "noto-serif-jp/700.ttf" }, "italic": "reuse-normal", "preview": "noto-serif-jp/preview.ttf" },
+ "Noto Sans KR": { "normal": { "400": "noto-sans-kr/400.ttf", "700": "noto-sans-kr/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-kr/preview.ttf" },
+ "Noto Serif KR": { "normal": { "400": "noto-serif-kr/400.ttf", "700": "noto-serif-kr/700.ttf" }, "italic": "reuse-normal", "preview": "noto-serif-kr/preview.ttf" },
+ "Noto Sans Arabic": { "normal": { "400": "noto-sans-arabic/400.ttf", "700": "noto-sans-arabic/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-arabic/preview.ttf" },
+ "Noto Naskh Arabic": { "normal": { "400": "noto-naskh-arabic/400.ttf", "700": "noto-naskh-arabic/700.ttf" }, "italic": "reuse-normal", "preview": "noto-naskh-arabic/preview.ttf" },
+ "Noto Sans Hebrew": { "normal": { "400": "noto-sans-hebrew/400.ttf", "700": "noto-sans-hebrew/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-hebrew/preview.ttf" },
+ "Noto Sans Thai": { "normal": { "400": "noto-sans-thai/400.ttf", "700": "noto-sans-thai/700.ttf" }, "italic": "reuse-normal", "preview": "noto-sans-thai/preview.ttf" },
+ "Noto Emoji": { "normal": { "400": "noto-emoji/400.ttf", "700": "noto-emoji/700.ttf" }, "italic": "reuse-normal", "preview": "noto-emoji/preview.ttf" }
+ }
+}
+```
+
+### Candidate source, license, script, and size evidence
+
+Sizes are `Content-Length` bytes from a HEAD request to the exact current catalog assets on 2026-09-06. Responses reported `Content-Encoding: gzip`; these are compressed transfer-size estimates, not a claim about the eventual on-disk representation. Preview paths are aliases to the selected 400 face and add no extra bytes when stored once. Primary sources: [IBM Plex LICENSE.txt](https://github.com/IBM/plex/blob/master/LICENSE.txt), [Noto core LICENSE](https://github.com/notofonts/noto-fonts/blob/main/LICENSE), [Noto CJK Sans LICENSE](https://github.com/notofonts/noto-cjk/blob/main/Sans/LICENSE), and [Noto Emoji font LICENSE](https://github.com/googlefonts/noto-emoji/blob/main/fonts/LICENSE).
+
+| Family | Style/weights in proposal | Current catalog source version | License | Script/fallback role | Gzip bytes (selected files) | Build owner; runtime owners |
+| --- | --- | --- | --- | --- | ---: | --- |
+| IBM Plex Serif | normal 400/700; italic 400/700 | `fonts.gstatic.com/s/ibmplexserif/v20` | OFL 1.1, Reserved Font Name `Plex` | Primary serif; Latin and punctuation stack | 294,717 | `packages/fonts`; `apps/web` FontDisplay; `packages/pdf` registration |
+| IBM Plex Sans | normal 400/700; italic 400/700 | `fonts.gstatic.com/s/ibmplexsans/v23` | OFL 1.1, Reserved Font Name `Plex` | Primary sans | 435,469 | `packages/fonts`; `apps/web` FontDisplay; `packages/pdf` registration |
+| Noto Serif | normal 400/700; italic 400/700 | `fonts.gstatic.com/s/notoserif/v33` | OFL 1.1 | Serif punctuation fallback | 1,055,120 | `packages/fonts`; `packages/pdf` fallback registration |
+| Noto Sans | normal 400/700; italic 400/700 | `fonts.gstatic.com/s/notosans/v42` | OFL 1.1 | Sans punctuation fallback | 1,236,259 | `packages/fonts`; `packages/pdf` fallback registration |
+| Noto Sans SC | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosanssc/v40` | OFL 1.1 (Noto CJK) | Simplified Han; CJK fallback | 12,766,416 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Serif SC | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoserifsc/v35` | OFL 1.1 (Noto CJK) | Simplified Han serif fallback | 17,350,185 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Sans TC | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosanstc/v39` | OFL 1.1 (Noto CJK) | Traditional Han fallback | 8,628,278 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Serif TC | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoseriftc/v36` | OFL 1.1 (Noto CJK) | Traditional Han serif fallback | 11,804,923 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Sans JP | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosansjp/v56` | OFL 1.1 (Noto CJK) | Kana and Japanese Han fallback | 6,383,035 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Serif JP | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoserifjp/v33` | OFL 1.1 (Noto CJK) | Kana and Japanese Han serif fallback | 8,685,862 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Sans KR | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosanskr/v39` | OFL 1.1 (Noto CJK) | Hangul and Korean Han fallback | 6,102,888 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Serif KR | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoserifkr/v31` | OFL 1.1 (Noto CJK) | Hangul and Korean Han serif fallback | 11,113,442 | `packages/fonts`; `packages/pdf` CJK fallback |
+| Noto Sans Arabic | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosansarabic/v33` | OFL 1.1 | Arabic sans fallback | 178,455 | `packages/fonts`; `packages/pdf` script fallback |
+| Noto Naskh Arabic | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notonaskharabic/v44` | OFL 1.1 | Arabic serif fallback | 190,924 | `packages/fonts`; `packages/pdf` script fallback |
+| Noto Sans Hebrew | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosanshebrew/v50` | OFL 1.1 | Hebrew fallback for both serif/sans slots | 55,707 | `packages/fonts`; `packages/pdf` script fallback |
+| Noto Sans Thai | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosansthai/v29` | OFL 1.1 | Thai fallback for both serif/sans slots | 55,173 | `packages/fonts`; `packages/pdf` script fallback |
+| Noto Emoji | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoemoji/v62` | OFL 1.1 for font files; assets/tools have separate licenses | Emoji outline fallback; verify renderer support | 1,153,847 | `packages/fonts`; `packages/pdf` script fallback |
+
+Estimated transfer size for all rows and listed styles: **87,490,700 bytes (~83.45 MiB)**. This confirms why a full-catalog bundle is out of scope. A later implementation should subset by declared glyph requirements or make the administrator choose fallback families; it must not silently fetch another CDN.
+
+### Source and license obligations
+
+- Pin an upstream release/commit and retain source attribution plus the complete applicable license with hosted assets. Do not use mutable `@latest` URLs as runtime sources.
+- IBM Plex’s license has Reserved Font Name `Plex`; modified/subset outputs must follow OFL naming requirements.
+- Noto core, Noto CJK, and Noto Emoji font files are OFL 1.1, but Noto Emoji documents separate Apache/public-domain treatment for tools and flag image assets. Bundle only font files unless those other assets are intentionally needed and separately attributed.
+- License checks are build-owner responsibility (`packages/fonts`/tooling); runtime owners (`apps/web` and `packages/pdf`) consume only the validated manifest.
+
+### Missing-family and missing-asset behavior
+
+Local mode must resolve only same-origin administrator-hosted manifest paths. If imported resume data names an unavailable family, show an actionable missing-family error naming the family and required local asset; apply a configured local fallback only when the administrator explicitly supplied one. If a required weight/style/fallback asset is absent, fail the affected preview/export with an actionable diagnostic containing family/style/weight and local path. Never retry Google Fonts, jsDelivr, or any other remote URL in local mode.
+
+Standard PDF families (Helvetica, Courier, Times-Roman) remain file-free. They do not prove that a document containing punctuation, CJK, Arabic, Hebrew, Thai, or emoji is network-free; the script fallback rows remain required.
+
+## Verification record
+
+Completed read-only checks before handoff:
+
+- CodeGraph exploration of font catalog, picker preview, browser PDF, server PDF, and existing fallback tests.
+- `pnpm dlx @tanstack/intent@latest list`: no matching local intent skill for this work.
+- `pnpm exec biome check tests/e2e/specs/offline-fonts.spec.ts tests/e2e/fixtures/offline-fonts.ts turbo.json`: passed.
+- `git diff --check`: passed.
+- `pnpm --filter @reactive-resume/fonts test`: passed (55 tests).
+- `pnpm --filter @reactive-resume/pdf exec vitest run src/hooks/use-register-fonts.test.ts`: passed (35 tests).
+- Web typography/regression suite: passed (940 tests across 135 files); web and server package typechecks passed.
+- `pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --list`: passed (4 diagnostic tests collected).
+- E2E diagnostic execution: blocked by missing build outputs and unavailable PostgreSQL/server; no success claim made.
+- `pnpm exec turbo boundaries`: unresolved repository checker failure (418 parser-panicked `.js` files, including existing PDF/web source mirrors); no boundary violation was reported for the new TypeScript imports.
+
+The implementation intentionally stops at diagnostic fixtures and manifest evidence. Shared source resolution, asset hosting, local-mode configuration, and production behavior remain Phase A step 3+ work.
diff --git a/tests/e2e/fixtures/offline-fonts.ts b/tests/e2e/fixtures/offline-fonts.ts
new file mode 100644
index 000000000..6ecc6978b
--- /dev/null
+++ b/tests/e2e/fixtures/offline-fonts.ts
@@ -0,0 +1,67 @@
+import type { Page } from "@playwright/test";
+import { Pool } from "pg";
+
+export const offlineFontScriptSamples = [
+ { name: "latin-punctuation", marker: "Latin punctuation • — “quotes” €" },
+ { name: "cjk", marker: "简体中文" },
+ { name: "arabic", marker: "العربية" },
+ { name: "hebrew", marker: "עברית" },
+ { name: "thai", marker: "ไทย" },
+ { name: "emoji", marker: "Emoji 🚀" },
+] as const;
+
+const offlineFontFixtureText = offlineFontScriptSamples.map(({ marker }) => marker).join(" | ");
+
+export type OfflineFontResumeFixture = {
+ builderURL: string;
+ slug: string;
+ username: string;
+};
+
+/** Seeds one stable multilingual document so each diagnostic surface uses identical glyph input. */
+export async function seedOfflineFontResume(page: Page): Promise {
+ const builderURL = page.url();
+ const resumeId = new URL(builderURL).pathname.split("/").at(-1);
+ const databaseURL = process.env.DATABASE_URL;
+ if (!resumeId || !databaseURL) throw new Error("Offline font fixture requires builder URL and DATABASE_URL.");
+
+ const pool = new Pool({ connectionString: databaseURL });
+ try {
+ const patches: Array<{ path: string[]; value: unknown }> = [
+ { path: ["picture", "hidden"], value: true },
+ { path: ["basics", "name"], value: "Offline Font Diagnostic Fixture" },
+ { path: ["basics", "headline"], value: offlineFontFixtureText },
+ { path: ["summary", "content"], value: `${offlineFontFixtureText}
` },
+ { path: ["metadata", "typography", "body", "fontFamily"], value: "IBM Plex Serif" },
+ { path: ["metadata", "typography", "body", "fontWeights"], value: ["400", "700"] },
+ { path: ["metadata", "typography", "heading", "fontFamily"], value: "IBM Plex Serif" },
+ { path: ["metadata", "typography", "heading", "fontWeights"], value: ["400", "700"] },
+ ];
+
+ for (const patch of patches) {
+ await pool.query(
+ `update "resume"
+ set data = jsonb_set(data, $2::text[], $3::jsonb, true),
+ updated_at = now()
+ where id = $1`,
+ [resumeId, patch.path, JSON.stringify(patch.value)],
+ );
+ }
+
+ await pool.query('update "resume" set is_public = true, updated_at = now() where id = $1', [resumeId]);
+ const result = await pool.query<{ slug: string; user_id: string }>(
+ 'select slug, user_id from "resume" where id = $1',
+ [resumeId],
+ );
+ const row = result.rows[0];
+ if (!row) throw new Error("Offline font fixture resume was not found after seeding.");
+
+ const owner = await pool.query<{ username: string }>('select username from "user" where id = $1', [row.user_id]);
+ const ownerUsername = owner.rows[0]?.username;
+ if (!ownerUsername) throw new Error("Offline font fixture owner was not found after seeding.");
+
+ return { builderURL, slug: row.slug, username: ownerUsername };
+ } finally {
+ await pool.end();
+ }
+}
diff --git a/tests/e2e/specs/offline-fonts.spec.ts b/tests/e2e/specs/offline-fonts.spec.ts
new file mode 100644
index 000000000..eb35cbbd0
--- /dev/null
+++ b/tests/e2e/specs/offline-fonts.spec.ts
@@ -0,0 +1,213 @@
+import type { Browser, BrowserContext, Page, TestInfo } from "@playwright/test";
+import { readFile, writeFile } from "node:fs/promises";
+import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
+import { offlineFontScriptSamples, seedOfflineFontResume } from "../fixtures/offline-fonts";
+import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
+import { expect, test } from "../fixtures/test";
+
+const diagnosticEnabled = process.env.OFFLINE_FONT_DIAGNOSTIC === "1";
+const serverRestartConfirmed = process.env.OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED === "1";
+
+type BlockedRequest = {
+ hostname: string;
+ path: string;
+};
+
+type ColdContext = {
+ context: BrowserContext;
+ blockedRequests: BlockedRequest[];
+};
+
+type PdfMarkers = Record<(typeof offlineFontScriptSamples)[number]["name"], boolean>;
+
+test.describe("offline font diagnostic", () => {
+ test.describe.configure({ mode: "serial" });
+ test.skip(!diagnosticEnabled, "Set OFFLINE_FONT_DIAGNOSTIC=1 to run network diagnostics.");
+ test.setTimeout(120_000);
+
+ async function createColdContext(browser: Browser, page: Page, testInfo: TestInfo): Promise {
+ const baseURL = String(testInfo.project.use.baseURL ?? "http://localhost:3000");
+ const allowedOrigin = new URL(baseURL).origin;
+ const context = await browser.newContext({
+ baseURL,
+ serviceWorkers: "block",
+ storageState: await page.context().storageState(),
+ });
+ const blockedRequests: BlockedRequest[] = [];
+
+ await context.route("**/*", async (route) => {
+ const requestURL = new URL(route.request().url());
+ if (requestURL.origin === allowedOrigin || requestURL.protocol === "data:" || requestURL.protocol === "blob:") {
+ await route.continue();
+ return;
+ }
+
+ blockedRequests.push({ hostname: requestURL.hostname, path: requestURL.pathname });
+ await route.abort("blockedbyclient");
+ });
+
+ return { context, blockedRequests };
+ }
+
+ function markerResult(text: string): PdfMarkers {
+ return Object.fromEntries(
+ offlineFontScriptSamples.map((sample) => [sample.name, text.includes(sample.marker)]),
+ ) as PdfMarkers;
+ }
+
+ function networkStatus(blockedRequests: BlockedRequest[]) {
+ return blockedRequests.length > 0 ? "network-error" : "no-browser-network-error";
+ }
+
+ async function readPdfText(bytes: Uint8Array) {
+ const loadingTask = getDocument({ data: bytes, useSystemFonts: false });
+ try {
+ const document = await loadingTask.promise;
+ const pages: string[] = [];
+ for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber += 1) {
+ const pdfPage = await document.getPage(pageNumber);
+ pages.push(...(await pdfPage.getTextContent()).items.flatMap((item) => ("str" in item ? [item.str] : [])));
+ }
+ return pages.join(" ");
+ } finally {
+ await loadingTask.destroy();
+ }
+ }
+
+ async function report(testInfo: TestInfo, name: string, reportData: Record) {
+ const body = JSON.stringify({
+ version: 1,
+ fixture: "offline-font-scripts-v1",
+ surface: name,
+ ...reportData,
+ });
+ expect(body).not.toMatch(/https?:\/\/|[?&](token|secret|password|auth)=/i);
+ await testInfo.attach(`${name}.json`, { body, contentType: "application/json" });
+ console.log(`[offline-fonts] ${body}`);
+ }
+
+ test("captures cold font picker preview requests", async ({ browser, authPage: seedPage }, testInfo) => {
+ await createSampleResumeFromDashboard(seedPage, testInfo);
+ const fixture = await seedOfflineFontResume(seedPage);
+ const cold = await createColdContext(browser, seedPage, testInfo);
+ const page = await cold.context.newPage();
+ try {
+ await page.goto(fixture.builderURL);
+ await openSidebarSection(page, "Typography");
+ await page.getByRole("combobox").first().click();
+ await expect(page.getByRole("option").first()).toBeVisible();
+ await page.waitForTimeout(1_000);
+ await report(testInfo, "picker-preview", {
+ cache: "new-browser-context",
+ blockedExternalFontRequests: cold.blockedRequests,
+ networkStatus: networkStatus(cold.blockedRequests),
+ glyphStatus: "not-applicable-picker-only",
+ });
+ } finally {
+ await cold.context.close();
+ }
+ });
+
+ test("captures cold builder PDF preview and classifies glyph/network results", async ({
+ browser,
+ authPage: seedPage,
+ }, testInfo) => {
+ await createSampleResumeFromDashboard(seedPage, testInfo);
+ const fixture = await seedOfflineFontResume(seedPage);
+ const cold = await createColdContext(browser, seedPage, testInfo);
+ const page = await cold.context.newPage();
+ try {
+ await page.goto(fixture.builderURL);
+ await page.waitForTimeout(5_000);
+ const canvasVisible = await page
+ .locator('[aria-hidden="false"] canvas')
+ .first()
+ .isVisible()
+ .catch(() => false);
+ await report(testInfo, "builder-preview", {
+ cache: "new-browser-context",
+ blockedExternalFontRequests: cold.blockedRequests,
+ networkStatus: networkStatus(cold.blockedRequests),
+ canvasVisible,
+ glyphStatus: "not-measured-preview-canvas",
+ });
+ } finally {
+ await cold.context.close();
+ }
+ });
+
+ test("captures cold browser PDF download and classifies extracted glyphs", async ({
+ browser,
+ authPage: seedPage,
+ }, testInfo) => {
+ await createSampleResumeFromDashboard(seedPage, testInfo);
+ const fixture = await seedOfflineFontResume(seedPage);
+ const cold = await createColdContext(browser, seedPage, testInfo);
+ const page = await cold.context.newPage();
+ let markerResultValue: PdfMarkers | null = null;
+ let downloadStatus = "not-started";
+ try {
+ await page.goto(fixture.builderURL);
+ await openSidebarSection(page, "Export");
+ await page.getByRole("button", { name: /Choose PDF, DOCX, Markdown, or JSON/ }).click();
+ const downloadPromise = page.waitForEvent("download", { timeout: 20_000 });
+ await page.getByRole("button", { name: "Download PDF", exact: true }).click();
+ const download = await downloadPromise;
+ downloadStatus = "received";
+ const path = testInfo.outputPath("offline-font-browser-download.pdf");
+ await download.saveAs(path);
+ markerResultValue = markerResult(await readPdfText(new Uint8Array(await readFile(path))));
+ } catch {
+ downloadStatus = "renderer-or-network-error";
+ } finally {
+ await report(testInfo, "browser-download", {
+ cache: "new-browser-context",
+ blockedExternalFontRequests: cold.blockedRequests,
+ networkStatus: networkStatus(cold.blockedRequests),
+ downloadStatus,
+ glyphStatus: markerResultValue ?? "not-extracted",
+ });
+ await cold.context.close();
+ }
+ });
+
+ test("exercises restarted-server PDF and records server observability boundary", async ({
+ browser,
+ authPage: seedPage,
+ }, testInfo) => {
+ await createSampleResumeFromDashboard(seedPage, testInfo);
+ const fixture = await seedOfflineFontResume(seedPage);
+ const cold = await createColdContext(browser, seedPage, testInfo);
+ const page = await cold.context.newPage();
+ let responseStatus = "not-requested";
+ let markerResultValue: PdfMarkers | null = null;
+ try {
+ await page.goto(fixture.builderURL);
+ if (!serverRestartConfirmed) {
+ responseStatus = "blocked-before-request";
+ } else {
+ const response = await page.request.get(
+ `/api/resumes/${encodeURIComponent(fixture.username)}/${encodeURIComponent(fixture.slug)}/pdf`,
+ );
+ responseStatus = String(response.status());
+ if (response.ok()) {
+ const bytes = await response.body();
+ await writeFile(testInfo.outputPath("offline-font-server.pdf"), bytes);
+ markerResultValue = markerResult(await readPdfText(new Uint8Array(bytes)));
+ }
+ }
+ } finally {
+ await report(testInfo, "server-pdf", {
+ cache: "new-browser-context; server-process-state-is-external",
+ serverRestartConfirmed,
+ blockedExternalFontRequests: cold.blockedRequests,
+ networkStatus: "server-outbound-requests-unobservable-from-playwright",
+ responseStatus,
+ glyphStatus: markerResultValue ?? "not-extracted",
+ limitation:
+ "Playwright route interception sees browser requests only; server fetches need a restarted process plus host-level egress capture.",
+ });
+ await cold.context.close();
+ }
+ });
+});
diff --git a/turbo.json b/turbo.json
index ee5384e87..d95fde1ec 100644
--- a/turbo.json
+++ b/turbo.json
@@ -89,7 +89,9 @@
"FLAG_SHOW_SPONSORS",
"FLAG_ALLOW_UNSAFE_OAUTH_REDIRECT_URI",
"FLAG_ALLOW_UNSAFE_AI_BASE_URL",
- "AI_TEST_TIMEOUT_MS"
+ "AI_TEST_TIMEOUT_MS",
+ "OFFLINE_FONT_DIAGNOSTIC",
+ "OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED"
],
"tasks": {
"transit": {
From ae8e2f76f1f6d49129f0f01050e39be0d4959fad Mon Sep 17 00:00:00 2001
From: Amruth Pillai
Date: Sun, 6 Sep 2026 05:30:40 +0200
Subject: [PATCH 2/4] test: remediate offline font diagnostic review
---
.orchestration/plan-27a-diagnostic.md | 14 +-
.orchestration/plan-27a-remediation.md | 24 +++
tests/e2e/fixtures/offline-fonts.ts | 4 +-
tests/e2e/specs/offline-fonts.spec.ts | 232 +++++++++++++++++++++++--
4 files changed, 251 insertions(+), 23 deletions(-)
create mode 100644 .orchestration/plan-27a-remediation.md
diff --git a/.orchestration/plan-27a-diagnostic.md b/.orchestration/plan-27a-diagnostic.md
index b3b9e869c..0a3ddccbb 100644
--- a/.orchestration/plan-27a-diagnostic.md
+++ b/.orchestration/plan-27a-diagnostic.md
@@ -34,11 +34,11 @@ The exact markers are versioned as `offline-font-scripts-v1`:
The four surfaces are separate tests:
1. Font picker preview opens Typography → Font Family and waits for lazy `FontFace` preview loads.
-2. Builder PDF preview navigates to the builder and records whether a PDF canvas appears.
-3. Browser PDF download uses the Export dialog and extracts marker presence from the downloaded PDF when generation succeeds.
-4. Server PDF calls the public PDF endpoint and extracts marker presence from its response when generation succeeds.
+2. Builder PDF preview navigates to the builder, captures the active PDF canvas, and measures marker-local raster crops.
+3. Browser PDF download uses the Export dialog, rasterizes the downloaded PDF, and measures marker-local crops when generation succeeds.
+4. Server PDF calls the public PDF endpoint and records text-layer marker presence when generation succeeds.
-PDF marker results classify missing text as a glyph result (`true`/`false` per marker), while blocked browser font requests classify the browser surface as `network-error`. The server report deliberately says `server-outbound-requests-unobservable-from-playwright`; it does not claim zero server requests.
+Builder/browser-PDF reports keep PDF text extraction as a separate `textLayerMarkers` signal; it does not prove visible glyph outlines. Raster evidence attaches a rendered PNG and per-marker crop metrics, failing for blank or tofu-like visible crops. Blocked browser font requests classify browser surfaces as `network-error`. The server report deliberately says `server-outbound-requests-unobservable-from-playwright`; its cold-network gate remains unresolved because server outbound capture and verifiable restart identity require external host-level controls.
## Run protocol and cold-cache boundary
@@ -49,7 +49,7 @@ OFFLINE_FONT_DIAGNOSTIC=1 OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED=1 \
pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --grep "picker preview"
```
-Stop and restart the production server before repeating the command with `builder PDF`, `browser PDF`, and `server PDF` grep patterns. The environment used for this change had no built `apps/server/dist` or `apps/web/dist`, no running PostgreSQL instance, and no production server to restart, so the cold E2E matrix was not run. This is an explicit infrastructure blocker, not a pass claim. The test still preserves the restart confirmation in its report (`serverRestartConfirmed`) for a later run.
+Stop and restart the production server before repeating the command with `builder PDF`, `browser PDF`, and `server PDF` grep patterns. The environment used for this change had no built `apps/server/dist` or `apps/web/dist`, no running PostgreSQL instance, and no production server to restart, so the cold E2E matrix was not run. This is an explicit infrastructure blocker, not a pass claim. The test records `serverRestartFlag` only as caller input and labels it non-proof; it does not claim a completed cold-network gate.
The current Playwright route guard cannot impose host-level egress denial on Node.js running the server. A genuinely cold server test therefore needs a separately restarted server plus host-level egress capture/deny (for example, a controlled network namespace or an approved outbound proxy). Do not infer server network behavior from an empty browser request list.
@@ -124,7 +124,7 @@ Sizes are `Content-Length` bytes from a HEAD request to the exact current catalo
| Noto Sans Thai | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notosansthai/v29` | OFL 1.1 | Thai fallback for both serif/sans slots | 55,173 | `packages/fonts`; `packages/pdf` script fallback |
| Noto Emoji | normal 400/700; italic reuses normal | `fonts.gstatic.com/s/notoemoji/v62` | OFL 1.1 for font files; assets/tools have separate licenses | Emoji outline fallback; verify renderer support | 1,153,847 | `packages/fonts`; `packages/pdf` script fallback |
-Estimated transfer size for all rows and listed styles: **87,490,700 bytes (~83.45 MiB)**. This confirms why a full-catalog bundle is out of scope. A later implementation should subset by declared glyph requirements or make the administrator choose fallback families; it must not silently fetch another CDN.
+Estimated transfer size for all rows and listed styles: **87,490,700 bytes (~83.44 MiB)**. This confirms why a full-catalog bundle is out of scope. A later implementation should subset by declared glyph requirements or make the administrator choose fallback families; it must not silently fetch another CDN.
### Source and license obligations
@@ -152,6 +152,6 @@ Completed read-only checks before handoff:
- Web typography/regression suite: passed (940 tests across 135 files); web and server package typechecks passed.
- `pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --list`: passed (4 diagnostic tests collected).
- E2E diagnostic execution: blocked by missing build outputs and unavailable PostgreSQL/server; no success claim made.
-- `pnpm exec turbo boundaries`: unresolved repository checker failure (418 parser-panicked `.js` files, including existing PDF/web source mirrors); no boundary violation was reported for the new TypeScript imports.
+- `pnpm exec turbo boundaries`: passed on fresh rerun (Turbo 2.10.12, 1108 files, no issues).
The implementation intentionally stops at diagnostic fixtures and manifest evidence. Shared source resolution, asset hosting, local-mode configuration, and production behavior remain Phase A step 3+ work.
diff --git a/.orchestration/plan-27a-remediation.md b/.orchestration/plan-27a-remediation.md
new file mode 100644
index 000000000..c96cd6ecb
--- /dev/null
+++ b/.orchestration/plan-27a-remediation.md
@@ -0,0 +1,24 @@
+# Plan 27A remediation
+
+Date: 2026-09-06
+Base: `61b58ae9a`
+Scope: concrete findings from `.orchestration/plan-27a-independent-review.md` only.
+
+## Remediated findings
+
+- Builder PDF preview and browser PDF download now produce raster evidence. The fixture stores each multilingual marker in its own summary paragraph, allowing the diagnostic to locate marker-local PDF text boxes and measure only those raster crops. Reports attach a rendered PNG plus per-marker `inkPixels`, trimmed dimensions, and status. Blank and tofu-like crops fail assertions; no whole-page snapshot is used.
+- PDF text extraction is reported separately as `textLayerMarkers`. It is not described or asserted as proof of visible glyph outlines.
+- Server PDF output remains text-extraction-only and is explicitly classified as `serverGateStatus: unresolved-external-host-level-blocker`. `serverRestartFlag` is caller input, not restart proof. Browser Playwright routing is not used to infer server egress, and no production resolver or instrumentation behavior was added.
+- `.orchestration/plan-27a-diagnostic.md` now records a fresh boundaries pass and the corrected `87,490,700 bytes (~83.44 MiB)` arithmetic.
+- Diagnostic remains opt-in through `OFFLINE_FONT_DIAGNOSTIC=1`; normal CI behavior remains unchanged. Request logs stay sanitized to hostname and pathname.
+
+## Verification
+
+- `pnpm exec biome check tests/e2e/specs/offline-fonts.spec.ts tests/e2e/fixtures/offline-fonts.ts` — passed.
+- `git diff --check` — passed.
+- `pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --list` — 4 tests collected.
+- `pnpm --filter @reactive-resume/fonts test` — 55/55 passed.
+- `pnpm --filter @reactive-resume/pdf exec vitest run src/hooks/use-register-fonts.test.ts` — 35/35 passed.
+- `pnpm exec turbo boundaries` — passed on fresh rerun (Turbo 2.10.12, 1108 files, no issues).
+
+Full diagnostic E2E remains unrun because this environment lacks production build output, PostgreSQL, and a production server. Server cold-network capture and verifiable restart identity remain external host-level blockers by design; this remediation does not claim that gate is complete.
diff --git a/tests/e2e/fixtures/offline-fonts.ts b/tests/e2e/fixtures/offline-fonts.ts
index 6ecc6978b..38275ed06 100644
--- a/tests/e2e/fixtures/offline-fonts.ts
+++ b/tests/e2e/fixtures/offline-fonts.ts
@@ -11,6 +11,7 @@ export const offlineFontScriptSamples = [
] as const;
const offlineFontFixtureText = offlineFontScriptSamples.map(({ marker }) => marker).join(" | ");
+const offlineFontFixtureSummary = offlineFontScriptSamples.map(({ marker }) => `${marker}
`).join("");
export type OfflineFontResumeFixture = {
builderURL: string;
@@ -31,7 +32,8 @@ export async function seedOfflineFontResume(page: Page): Promise${offlineFontFixtureText}
` },
+ // Keep markers in separate blocks so raster evidence can map each script to a local PDF crop.
+ { path: ["summary", "content"], value: offlineFontFixtureSummary },
{ path: ["metadata", "typography", "body", "fontFamily"], value: "IBM Plex Serif" },
{ path: ["metadata", "typography", "body", "fontWeights"], value: ["400", "700"] },
{ path: ["metadata", "typography", "heading", "fontFamily"], value: "IBM Plex Serif" },
diff --git a/tests/e2e/specs/offline-fonts.spec.ts b/tests/e2e/specs/offline-fonts.spec.ts
index eb35cbbd0..f1bda9261 100644
--- a/tests/e2e/specs/offline-fonts.spec.ts
+++ b/tests/e2e/specs/offline-fonts.spec.ts
@@ -1,12 +1,13 @@
import type { Browser, BrowserContext, Page, TestInfo } from "@playwright/test";
import { readFile, writeFile } from "node:fs/promises";
+import { createRequire } from "node:module";
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
import { offlineFontScriptSamples, seedOfflineFontResume } from "../fixtures/offline-fonts";
import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
import { expect, test } from "../fixtures/test";
const diagnosticEnabled = process.env.OFFLINE_FONT_DIAGNOSTIC === "1";
-const serverRestartConfirmed = process.env.OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED === "1";
+const serverRestartFlag = process.env.OFFLINE_FONT_DIAGNOSTIC_SERVER_RESTARTED === "1";
type BlockedRequest = {
hostname: string;
@@ -20,6 +21,21 @@ type ColdContext = {
type PdfMarkers = Record<(typeof offlineFontScriptSamples)[number]["name"], boolean>;
+type RasterGlyphEvidence = {
+ name: (typeof offlineFontScriptSamples)[number]["name"];
+ status: "visible" | "blank" | "tofu-like" | "not-located";
+ inkPixels: number;
+ trimmedWidth: number;
+ trimmedHeight: number;
+};
+
+type PdfRasterEvidence = {
+ rasterDataUrl: string;
+ textLayerMarkers: PdfMarkers;
+ referenceGlyphs: RasterGlyphEvidence[];
+ previewGlyphs: RasterGlyphEvidence[];
+};
+
test.describe("offline font diagnostic", () => {
test.describe.configure({ mode: "serial" });
test.skip(!diagnosticEnabled, "Set OFFLINE_FONT_DIAGNOSTIC=1 to run network diagnostics.");
@@ -49,7 +65,7 @@ test.describe("offline font diagnostic", () => {
return { context, blockedRequests };
}
- function markerResult(text: string): PdfMarkers {
+ function extractedMarkerResult(text: string): PdfMarkers {
return Object.fromEntries(
offlineFontScriptSamples.map((sample) => [sample.name, text.includes(sample.marker)]),
) as PdfMarkers;
@@ -74,6 +90,165 @@ test.describe("offline font diagnostic", () => {
}
}
+ async function capturePdfBytes(page: Page) {
+ await page.addInitScript(() => {
+ const read = Blob.prototype.arrayBuffer;
+ Blob.prototype.arrayBuffer = async function () {
+ const buffer = await read.call(this);
+ const bytes = new Uint8Array(buffer);
+ if (String.fromCharCode(...bytes.subarray(0, 5)) === "%PDF-") {
+ (window as Window & { offlineFontPdfBytes?: number[] }).offlineFontPdfBytes = Array.from(bytes);
+ }
+ return buffer;
+ };
+ });
+ }
+
+ async function renderPdfRasterEvidence(page: Page, bytes: Uint8Array): Promise {
+ const require = createRequire(`${process.cwd()}/package.json`);
+ await page.route("**/__offline_font_pdfjs/*", async (route) => {
+ const worker = new URL(route.request().url()).pathname.endsWith("worker.mjs");
+ await route.fulfill({
+ contentType: "text/javascript",
+ path: require.resolve(`pdfjs-dist/legacy/build/${worker ? "pdf.worker.mjs" : "pdf.mjs"}`),
+ });
+ });
+
+ return page.evaluate(
+ async ({ bytes, markers }) => {
+ const moduleUrl = `${location.origin}/__offline_font_pdfjs/pdf.mjs`;
+ const pdfjs: typeof import("pdfjs-dist/legacy/build/pdf.mjs") = await import(moduleUrl);
+ pdfjs.GlobalWorkerOptions.workerSrc = `${location.origin}/__offline_font_pdfjs/worker.mjs`;
+ const loadingTask = pdfjs.getDocument({ data: Uint8Array.from(bytes), useSystemFonts: false });
+ try {
+ const pdfDocument = await loadingTask.promise;
+ const pdfPage = await pdfDocument.getPage(1);
+ const textContent = await pdfPage.getTextContent();
+ const textItems = textContent.items.flatMap((item) =>
+ "str" in item
+ ? [
+ {
+ str: item.str,
+ x: item.transform[4] ?? 0,
+ y: item.transform[5] ?? 0,
+ width: item.width,
+ height: Math.max(item.height, Math.abs(item.transform[3] ?? 0), 1),
+ },
+ ]
+ : [],
+ );
+ const textLayer = textItems.map(({ str }) => str).join(" ");
+ const baseViewport = pdfPage.getViewport({ scale: 1 });
+ const rasterScale = 4;
+ const viewport = pdfPage.getViewport({ scale: rasterScale });
+ const raster = globalThis.document.createElement("canvas");
+ raster.width = Math.ceil(viewport.width);
+ raster.height = Math.ceil(viewport.height);
+ const rasterContext = raster.getContext("2d");
+ if (!rasterContext) throw new Error("Missing PDF raster context.");
+ await pdfPage.render({
+ canvas: raster,
+ canvasContext: rasterContext,
+ viewport,
+ annotationMode: pdfjs.AnnotationMode.DISABLE,
+ background: "white",
+ }).promise;
+
+ const boxes = markers.map((sample) => {
+ const item = textItems.find(({ str }) => str.includes(sample.marker));
+ if (!item) return { name: sample.name, marker: sample.marker, box: null };
+ const height = Math.max(item.height, 1);
+ const baseline = baseViewport.height - item.y;
+ return {
+ name: sample.name,
+ marker: sample.marker,
+ box: {
+ x: item.x,
+ y: baseline - height,
+ width: Math.max(item.width, height),
+ height,
+ },
+ };
+ });
+
+ function measure(canvas: HTMLCanvasElement, scale: number): RasterGlyphEvidence[] {
+ const context = canvas.getContext("2d");
+ if (!context) throw new Error("Missing PDF preview raster context.");
+ const pixels = context.getImageData(0, 0, canvas.width, canvas.height);
+ return boxes.map(({ name, box }) => {
+ if (!box) return { name, status: "not-located", inkPixels: 0, trimmedWidth: 0, trimmedHeight: 0 };
+ const padding = 2 * scale;
+ const left = Math.max(0, Math.floor(box.x * scale - padding));
+ const top = Math.max(0, Math.floor(box.y * scale - padding));
+ const right = Math.min(canvas.width, Math.ceil((box.x + box.width) * scale + padding));
+ const bottom = Math.min(canvas.height, Math.ceil((box.y + box.height) * scale + padding));
+ let inkPixels = 0;
+ let minX = right;
+ let minY = bottom;
+ let maxX = left;
+ let maxY = top;
+ for (let y = top; y < bottom; y += 1) {
+ for (let x = left; x < right; x += 1) {
+ const index = (y * pixels.width + x) * 4;
+ const red = pixels.data[index] ?? 255;
+ const green = pixels.data[index + 1] ?? 255;
+ const blue = pixels.data[index + 2] ?? 255;
+ if (red >= 245 && green >= 245 && blue >= 245) continue;
+ inkPixels += 1;
+ minX = Math.min(minX, x);
+ minY = Math.min(minY, y);
+ maxX = Math.max(maxX, x);
+ maxY = Math.max(maxY, y);
+ }
+ }
+ if (inkPixels === 0) {
+ return { name, status: "blank", inkPixels, trimmedWidth: 0, trimmedHeight: 0 };
+ }
+ let interiorInk = 0;
+ for (
+ let y = minY + Math.floor((maxY - minY + 1) * 0.2);
+ y < maxY - Math.floor((maxY - minY + 1) * 0.2);
+ y += 1
+ ) {
+ for (
+ let x = minX + Math.floor((maxX - minX + 1) * 0.2);
+ x < maxX - Math.floor((maxX - minX + 1) * 0.2);
+ x += 1
+ ) {
+ const index = (y * pixels.width + x) * 4;
+ if (
+ (pixels.data[index] ?? 255) < 245 ||
+ (pixels.data[index + 1] ?? 255) < 245 ||
+ (pixels.data[index + 2] ?? 255) < 245
+ )
+ interiorInk += 1;
+ }
+ }
+ const trimmedWidth = maxX - minX + 1;
+ const trimmedHeight = maxY - minY + 1;
+ const interiorRatio = interiorInk / Math.max(inkPixels, 1);
+ const status = interiorRatio < 0.08 && trimmedWidth >= 8 && trimmedHeight >= 8 ? "tofu-like" : "visible";
+ return { name, status, inkPixels, trimmedWidth, trimmedHeight };
+ });
+ }
+
+ const preview = document.querySelector('[aria-hidden="false"] canvas');
+ return {
+ rasterDataUrl: raster.toDataURL(),
+ textLayerMarkers: Object.fromEntries(
+ markers.map((sample) => [sample.name, textLayer.includes(sample.marker)]),
+ ),
+ referenceGlyphs: measure(raster, rasterScale),
+ previewGlyphs: preview ? measure(preview, preview.width / baseViewport.width) : [],
+ };
+ } finally {
+ await loadingTask.destroy();
+ }
+ },
+ { bytes: Array.from(bytes), markers: offlineFontScriptSamples },
+ );
+ }
+
async function report(testInfo: TestInfo, name: string, reportData: Record) {
const body = JSON.stringify({
version: 1,
@@ -116,20 +291,33 @@ test.describe("offline font diagnostic", () => {
const fixture = await seedOfflineFontResume(seedPage);
const cold = await createColdContext(browser, seedPage, testInfo);
const page = await cold.context.newPage();
+ await capturePdfBytes(page);
try {
await page.goto(fixture.builderURL);
await page.waitForTimeout(5_000);
- const canvasVisible = await page
- .locator('[aria-hidden="false"] canvas')
- .first()
- .isVisible()
- .catch(() => false);
+ const previewCanvas = page.locator('[aria-hidden="false"] canvas').first();
+ const canvasVisible = await previewCanvas.isVisible().catch(() => false);
+ const pdfBytes = await page.evaluate(
+ () => (window as Window & { offlineFontPdfBytes?: number[] }).offlineFontPdfBytes,
+ );
+ expect(pdfBytes).toBeDefined();
+ const rasterEvidence = pdfBytes ? await renderPdfRasterEvidence(page, Uint8Array.from(pdfBytes)) : null;
+ if (rasterEvidence) {
+ await testInfo.attach("builder-preview-raster.png", {
+ body: Buffer.from(rasterEvidence.rasterDataUrl.split(",")[1] ?? "", "base64"),
+ contentType: "image/png",
+ });
+ expect(rasterEvidence.previewGlyphs).toHaveLength(offlineFontScriptSamples.length);
+ expect(rasterEvidence.previewGlyphs.every(({ status }) => status === "visible")).toBe(true);
+ }
+ expect(canvasVisible).toBe(true);
await report(testInfo, "builder-preview", {
cache: "new-browser-context",
blockedExternalFontRequests: cold.blockedRequests,
networkStatus: networkStatus(cold.blockedRequests),
canvasVisible,
- glyphStatus: "not-measured-preview-canvas",
+ textLayerMarkers: rasterEvidence?.textLayerMarkers ?? "not-extracted",
+ glyphStatus: rasterEvidence?.previewGlyphs ?? "not-rasterized",
});
} finally {
await cold.context.close();
@@ -145,6 +333,7 @@ test.describe("offline font diagnostic", () => {
const cold = await createColdContext(browser, seedPage, testInfo);
const page = await cold.context.newPage();
let markerResultValue: PdfMarkers | null = null;
+ let rasterEvidence: PdfRasterEvidence | null = null;
let downloadStatus = "not-started";
try {
await page.goto(fixture.builderURL);
@@ -156,7 +345,13 @@ test.describe("offline font diagnostic", () => {
downloadStatus = "received";
const path = testInfo.outputPath("offline-font-browser-download.pdf");
await download.saveAs(path);
- markerResultValue = markerResult(await readPdfText(new Uint8Array(await readFile(path))));
+ const bytes = new Uint8Array(await readFile(path));
+ markerResultValue = extractedMarkerResult(await readPdfText(bytes));
+ rasterEvidence = await renderPdfRasterEvidence(page, bytes);
+ await testInfo.attach("browser-download-raster.png", {
+ body: Buffer.from(rasterEvidence.rasterDataUrl.split(",")[1] ?? "", "base64"),
+ contentType: "image/png",
+ });
} catch {
downloadStatus = "renderer-or-network-error";
} finally {
@@ -165,10 +360,15 @@ test.describe("offline font diagnostic", () => {
blockedExternalFontRequests: cold.blockedRequests,
networkStatus: networkStatus(cold.blockedRequests),
downloadStatus,
- glyphStatus: markerResultValue ?? "not-extracted",
+ textLayerMarkers: markerResultValue ?? "not-extracted",
+ glyphStatus: rasterEvidence?.referenceGlyphs ?? "not-rasterized",
});
await cold.context.close();
}
+ if (rasterEvidence) {
+ expect(rasterEvidence.referenceGlyphs).toHaveLength(offlineFontScriptSamples.length);
+ expect(rasterEvidence.referenceGlyphs.every(({ status }) => status === "visible")).toBe(true);
+ }
});
test("exercises restarted-server PDF and records server observability boundary", async ({
@@ -183,7 +383,7 @@ test.describe("offline font diagnostic", () => {
let markerResultValue: PdfMarkers | null = null;
try {
await page.goto(fixture.builderURL);
- if (!serverRestartConfirmed) {
+ if (!serverRestartFlag) {
responseStatus = "blocked-before-request";
} else {
const response = await page.request.get(
@@ -193,19 +393,21 @@ test.describe("offline font diagnostic", () => {
if (response.ok()) {
const bytes = await response.body();
await writeFile(testInfo.outputPath("offline-font-server.pdf"), bytes);
- markerResultValue = markerResult(await readPdfText(new Uint8Array(bytes)));
+ markerResultValue = extractedMarkerResult(await readPdfText(new Uint8Array(bytes)));
}
}
} finally {
await report(testInfo, "server-pdf", {
cache: "new-browser-context; server-process-state-is-external",
- serverRestartConfirmed,
+ serverRestartFlag,
+ serverGateStatus: "unresolved-external-host-level-blocker",
blockedExternalFontRequests: cold.blockedRequests,
networkStatus: "server-outbound-requests-unobservable-from-playwright",
responseStatus,
- glyphStatus: markerResultValue ?? "not-extracted",
+ textLayerMarkers: markerResultValue ?? "not-extracted",
+ glyphStatus: "not-rasterized-server-surface",
limitation:
- "Playwright route interception sees browser requests only; server fetches need a restarted process plus host-level egress capture.",
+ "Server outbound request capture and verifiable restart identity remain unresolved external host-level blockers; the caller flag is not restart proof. Playwright route interception sees browser requests only; do not treat this run as a cold-network gate.",
});
await cold.context.close();
}
From c8a10b3d3b3197ac1940b9f7498b290f3f93c528 Mon Sep 17 00:00:00 2001
From: Amruth Pillai
Date: Sun, 6 Sep 2026 05:47:56 +0200
Subject: [PATCH 3/4] test: harden offline font raster evidence
---
.../plan-27a-remediation-round-2.md | 22 ++++
.orchestration/plan-27a-remediation.md | 4 +-
.../e2e/fixtures/offline-font-markers.test.ts | 44 +++++++
tests/e2e/fixtures/offline-font-markers.ts | 114 +++++++++++++++++
tests/e2e/fixtures/offline-fonts.ts | 5 +-
tests/e2e/specs/offline-fonts.spec.ts | 115 +++++++++++-------
6 files changed, 252 insertions(+), 52 deletions(-)
create mode 100644 .orchestration/plan-27a-remediation-round-2.md
create mode 100644 tests/e2e/fixtures/offline-font-markers.test.ts
create mode 100644 tests/e2e/fixtures/offline-font-markers.ts
diff --git a/.orchestration/plan-27a-remediation-round-2.md b/.orchestration/plan-27a-remediation-round-2.md
new file mode 100644
index 000000000..5fa66ff0c
--- /dev/null
+++ b/.orchestration/plan-27a-remediation-round-2.md
@@ -0,0 +1,22 @@
+# Plan 27A remediation round 2
+
+Date: 2026-09-06
+Base: `ae8e2f76f`
+
+## Focused fixes
+
+- Removed multilingual markers from the fixture headline. Each marker now exists only in its dedicated summary paragraph.
+- Added pure marker-location helpers. Marker lookup joins PDF text items, supports markers split across items, rejects duplicate occurrences, and rejects non-whitespace neighbors that could contaminate a local crop.
+- Raster measurement still scans with antialiasing padding but counts ink only inside the marker box, preventing neighboring glyphs from making blank or tofu-like evidence pass.
+- Browser PDF download now separates download errors from post-download evidence errors. A received download with failed rasterization is reported as `unresolved-raster-evidence-error` and fails the opt-in test rather than passing as a generic download error.
+- Added focused pure tests covering duplicate, split, neighboring, blank, and tofu-like cases.
+- Removed trailing spaces from `plan-27a-remediation.md`.
+
+## Verification
+
+- `pnpm exec vitest run tests/e2e/fixtures/offline-font-markers.test.ts` — 5/5 passed.
+- `pnpm exec biome check tests/e2e/specs/offline-fonts.spec.ts tests/e2e/fixtures/offline-fonts.ts tests/e2e/fixtures/offline-font-markers.ts tests/e2e/fixtures/offline-font-markers.test.ts` — passed.
+- `pnpm exec playwright test tests/e2e/specs/offline-fonts.spec.ts --list` — 4 tests collected.
+- `git diff --check` — passed after remediation-document whitespace cleanup.
+
+Full diagnostic E2E remains opt-in and was not run in this focused round. Server outbound request capture and verifiable restart identity remain explicit external host-level blockers; no production resolver changes were made.
diff --git a/.orchestration/plan-27a-remediation.md b/.orchestration/plan-27a-remediation.md
index c96cd6ecb..f445acf14 100644
--- a/.orchestration/plan-27a-remediation.md
+++ b/.orchestration/plan-27a-remediation.md
@@ -1,7 +1,7 @@
# Plan 27A remediation
-Date: 2026-09-06
-Base: `61b58ae9a`
+Date: 2026-09-06
+Base: `61b58ae9a`
Scope: concrete findings from `.orchestration/plan-27a-independent-review.md` only.
## Remediated findings
diff --git a/tests/e2e/fixtures/offline-font-markers.test.ts b/tests/e2e/fixtures/offline-font-markers.test.ts
new file mode 100644
index 000000000..3a0462dd5
--- /dev/null
+++ b/tests/e2e/fixtures/offline-font-markers.test.ts
@@ -0,0 +1,44 @@
+import { describe, expect, it } from "vitest";
+import { classifyRasterInk, locatePdfMarkerBoxes } from "./offline-font-markers";
+
+const marker = { name: "cjk", marker: "简体中文" } as const;
+
+const item = (str: string, x = 10, y = 100, width = str.length * 5) => ({
+ str,
+ x,
+ y,
+ width,
+ height: 12,
+});
+
+describe("locatePdfMarkerBoxes", () => {
+ it("rejects duplicate marker sources instead of selecting an arbitrary crop", () => {
+ const result = locatePdfMarkerBoxes([item(marker.marker), item(marker.marker, 10, 70)], [marker], 200);
+
+ expect(result).toEqual([{ ...marker, box: null }]);
+ });
+
+ it("unions text items when one marker is split across PDF text items", () => {
+ const result = locatePdfMarkerBoxes([item("简体"), item("中文", 20)], [marker], 200);
+
+ expect(result[0]?.box).toEqual({ x: 10, y: 88, width: 20, height: 12 });
+ });
+
+ it("rejects text-item neighbors that could contaminate marker-local raster evidence", () => {
+ const result = locatePdfMarkerBoxes([item(`prefix${marker.marker}suffix`)], [marker], 200);
+
+ expect(result).toEqual([{ ...marker, box: null }]);
+ });
+});
+
+describe("classifyRasterInk", () => {
+ it("does not treat a blank crop as visible", () => {
+ expect(classifyRasterInk({ inkPixels: 0, interiorInk: 0, trimmedWidth: 0, trimmedHeight: 0 })).toBe("blank");
+ });
+
+ it("does not treat tofu-like outline as visible", () => {
+ expect(classifyRasterInk({ inkPixels: 100, interiorInk: 0, trimmedWidth: 20, trimmedHeight: 20 })).toBe(
+ "tofu-like",
+ );
+ });
+});
diff --git a/tests/e2e/fixtures/offline-font-markers.ts b/tests/e2e/fixtures/offline-font-markers.ts
new file mode 100644
index 000000000..a1d6037e4
--- /dev/null
+++ b/tests/e2e/fixtures/offline-font-markers.ts
@@ -0,0 +1,114 @@
+export type PdfTextItem = {
+ str: string;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+};
+
+export type PdfMarker = {
+ name: string;
+ marker: string;
+};
+
+export type PdfMarkerBox = {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+};
+
+export type PdfMarkerLocation = TMarker & {
+ box: PdfMarkerBox | null;
+};
+
+export type RasterInkMeasurements = {
+ inkPixels: number;
+ interiorInk: number;
+ trimmedWidth: number;
+ trimmedHeight: number;
+};
+
+export type RasterGlyphStatus = "visible" | "blank" | "tofu-like";
+
+type TextItemRange = PdfTextItem & {
+ start: number;
+ end: number;
+};
+
+const textItemRanges = (textItems: PdfTextItem[]): TextItemRange[] => {
+ let offset = 0;
+ return textItems.map((item) => {
+ const start = offset;
+ offset += item.str.length;
+ return { ...item, start, end: offset };
+ });
+};
+
+const findMarkerOccurrences = (text: string, marker: string): number[] => {
+ if (marker.length === 0) return [];
+
+ const occurrences: number[] = [];
+ let start = 0;
+ while (start <= text.length - marker.length) {
+ const match = text.indexOf(marker, start);
+ if (match === -1) break;
+ occurrences.push(match);
+ start = match + marker.length;
+ }
+ return occurrences;
+};
+
+export const locatePdfMarkerBoxes = (
+ textItems: PdfTextItem[],
+ markers: readonly TMarker[],
+ pageHeight: number,
+): PdfMarkerLocation[] => {
+ const ranges = textItemRanges(textItems);
+ const text = textItems.map((item) => item.str).join("");
+
+ return markers.map((marker) => {
+ const [start] = findMarkerOccurrences(text, marker.marker);
+ const end = start === undefined ? undefined : start + marker.marker.length;
+ if (start === undefined || end === undefined || findMarkerOccurrences(text, marker.marker).length !== 1) {
+ return { ...marker, box: null };
+ }
+
+ const coveredRanges = ranges.filter((range) => range.start < end && range.end > start);
+ const firstRange = coveredRanges[0];
+ const lastRange = coveredRanges.at(-1);
+ if (!firstRange || !lastRange) return { ...marker, box: null };
+
+ const firstLocalStart = start - firstRange.start;
+ const lastLocalEnd = end - lastRange.start;
+ const leadingText = firstRange.str.slice(0, firstLocalStart);
+ const trailingText = lastRange.str.slice(lastLocalEnd);
+ if (leadingText.trim() || trailingText.trim()) return { ...marker, box: null };
+
+ const left = Math.min(...coveredRanges.map((range) => range.x));
+ const top = Math.min(...coveredRanges.map((range) => pageHeight - range.y - Math.max(range.height, 1)));
+ const right = Math.max(...coveredRanges.map((range) => range.x + Math.max(range.width, 1)));
+ const bottom = Math.max(...coveredRanges.map((range) => pageHeight - range.y));
+
+ return {
+ ...marker,
+ box: {
+ x: left,
+ y: top,
+ width: Math.max(right - left, 1),
+ height: Math.max(bottom - top, 1),
+ },
+ };
+ });
+};
+
+export const classifyRasterInk = ({
+ inkPixels,
+ interiorInk,
+ trimmedWidth,
+ trimmedHeight,
+}: RasterInkMeasurements): RasterGlyphStatus => {
+ if (inkPixels === 0) return "blank";
+ const interiorRatio = interiorInk / Math.max(inkPixels, 1);
+ return interiorRatio < 0.08 && trimmedWidth >= 8 && trimmedHeight >= 8 ? "tofu-like" : "visible";
+};
diff --git a/tests/e2e/fixtures/offline-fonts.ts b/tests/e2e/fixtures/offline-fonts.ts
index 38275ed06..912a3ea94 100644
--- a/tests/e2e/fixtures/offline-fonts.ts
+++ b/tests/e2e/fixtures/offline-fonts.ts
@@ -10,7 +10,6 @@ export const offlineFontScriptSamples = [
{ name: "emoji", marker: "Emoji 🚀" },
] as const;
-const offlineFontFixtureText = offlineFontScriptSamples.map(({ marker }) => marker).join(" | ");
const offlineFontFixtureSummary = offlineFontScriptSamples.map(({ marker }) => `${marker}
`).join("");
export type OfflineFontResumeFixture = {
@@ -31,8 +30,8 @@ export async function seedOfflineFontResume(page: Page): Promise = [
{ path: ["picture", "hidden"], value: true },
{ path: ["basics", "name"], value: "Offline Font Diagnostic Fixture" },
- { path: ["basics", "headline"], value: offlineFontFixtureText },
- // Keep markers in separate blocks so raster evidence can map each script to a local PDF crop.
+ { path: ["basics", "headline"], value: "Offline font diagnostic" },
+ // Keep each marker in one dedicated block so raster evidence can map each script to a local PDF crop.
{ path: ["summary", "content"], value: offlineFontFixtureSummary },
{ path: ["metadata", "typography", "body", "fontFamily"], value: "IBM Plex Serif" },
{ path: ["metadata", "typography", "body", "fontWeights"], value: ["400", "700"] },
diff --git a/tests/e2e/specs/offline-fonts.spec.ts b/tests/e2e/specs/offline-fonts.spec.ts
index f1bda9261..faec317e5 100644
--- a/tests/e2e/specs/offline-fonts.spec.ts
+++ b/tests/e2e/specs/offline-fonts.spec.ts
@@ -2,6 +2,7 @@ import type { Browser, BrowserContext, Page, TestInfo } from "@playwright/test";
import { readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
+import { locatePdfMarkerBoxes } from "../fixtures/offline-font-markers";
import { offlineFontScriptSamples, seedOfflineFontResume } from "../fixtures/offline-fonts";
import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
import { expect, test } from "../fixtures/test";
@@ -82,9 +83,11 @@ test.describe("offline font diagnostic", () => {
const pages: string[] = [];
for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber += 1) {
const pdfPage = await document.getPage(pageNumber);
- pages.push(...(await pdfPage.getTextContent()).items.flatMap((item) => ("str" in item ? [item.str] : [])));
+ pages.push(
+ (await pdfPage.getTextContent()).items.flatMap((item) => ("str" in item ? [item.str] : [])).join(""),
+ );
}
- return pages.join(" ");
+ return pages.join("\n");
} finally {
await loadingTask.destroy();
}
@@ -114,8 +117,8 @@ test.describe("offline font diagnostic", () => {
});
});
- return page.evaluate(
- async ({ bytes, markers }) => {
+ const textEvidence = await page.evaluate(
+ async ({ bytes }) => {
const moduleUrl = `${location.origin}/__offline_font_pdfjs/pdf.mjs`;
const pdfjs: typeof import("pdfjs-dist/legacy/build/pdf.mjs") = await import(moduleUrl);
pdfjs.GlobalWorkerOptions.workerSrc = `${location.origin}/__offline_font_pdfjs/worker.mjs`;
@@ -124,20 +127,40 @@ test.describe("offline font diagnostic", () => {
const pdfDocument = await loadingTask.promise;
const pdfPage = await pdfDocument.getPage(1);
const textContent = await pdfPage.getTextContent();
- const textItems = textContent.items.flatMap((item) =>
- "str" in item
- ? [
- {
- str: item.str,
- x: item.transform[4] ?? 0,
- y: item.transform[5] ?? 0,
- width: item.width,
- height: Math.max(item.height, Math.abs(item.transform[3] ?? 0), 1),
- },
- ]
- : [],
- );
- const textLayer = textItems.map(({ str }) => str).join(" ");
+ return {
+ pageHeight: pdfPage.getViewport({ scale: 1 }).height,
+ textItems: textContent.items.flatMap((item) =>
+ "str" in item
+ ? [
+ {
+ str: item.str,
+ x: item.transform[4] ?? 0,
+ y: item.transform[5] ?? 0,
+ width: item.width,
+ height: Math.max(item.height, Math.abs(item.transform[3] ?? 0), 1),
+ },
+ ]
+ : [],
+ ),
+ };
+ } finally {
+ await loadingTask.destroy();
+ }
+ },
+ { bytes: Array.from(bytes) },
+ );
+ const boxes = locatePdfMarkerBoxes(textEvidence.textItems, offlineFontScriptSamples, textEvidence.pageHeight);
+ const textLayerMarkers = extractedMarkerResult(textEvidence.textItems.map(({ str }) => str).join(""));
+
+ const rasterEvidence = await page.evaluate(
+ async ({ bytes, boxes }) => {
+ const moduleUrl = `${location.origin}/__offline_font_pdfjs/pdf.mjs`;
+ const pdfjs: typeof import("pdfjs-dist/legacy/build/pdf.mjs") = await import(moduleUrl);
+ pdfjs.GlobalWorkerOptions.workerSrc = `${location.origin}/__offline_font_pdfjs/worker.mjs`;
+ const loadingTask = pdfjs.getDocument({ data: Uint8Array.from(bytes), useSystemFonts: false });
+ try {
+ const pdfDocument = await loadingTask.promise;
+ const pdfPage = await pdfDocument.getPage(1);
const baseViewport = pdfPage.getViewport({ scale: 1 });
const rasterScale = 4;
const viewport = pdfPage.getViewport({ scale: rasterScale });
@@ -154,29 +177,16 @@ test.describe("offline font diagnostic", () => {
background: "white",
}).promise;
- const boxes = markers.map((sample) => {
- const item = textItems.find(({ str }) => str.includes(sample.marker));
- if (!item) return { name: sample.name, marker: sample.marker, box: null };
- const height = Math.max(item.height, 1);
- const baseline = baseViewport.height - item.y;
- return {
- name: sample.name,
- marker: sample.marker,
- box: {
- x: item.x,
- y: baseline - height,
- width: Math.max(item.width, height),
- height,
- },
- };
- });
-
function measure(canvas: HTMLCanvasElement, scale: number): RasterGlyphEvidence[] {
const context = canvas.getContext("2d");
if (!context) throw new Error("Missing PDF preview raster context.");
const pixels = context.getImageData(0, 0, canvas.width, canvas.height);
return boxes.map(({ name, box }) => {
if (!box) return { name, status: "not-located", inkPixels: 0, trimmedWidth: 0, trimmedHeight: 0 };
+ const markerLeft = box.x * scale;
+ const markerTop = box.y * scale;
+ const markerRight = (box.x + box.width) * scale;
+ const markerBottom = (box.y + box.height) * scale;
const padding = 2 * scale;
const left = Math.max(0, Math.floor(box.x * scale - padding));
const top = Math.max(0, Math.floor(box.y * scale - padding));
@@ -189,6 +199,9 @@ test.describe("offline font diagnostic", () => {
let maxY = top;
for (let y = top; y < bottom; y += 1) {
for (let x = left; x < right; x += 1) {
+ // Scan with padding for antialiasing, but count ink only inside marker box.
+ // Neighboring glyphs must never make blank/tofu marker evidence pass.
+ if (x < markerLeft || x >= markerRight || y < markerTop || y >= markerBottom) continue;
const index = (y * pixels.width + x) * 4;
const red = pixels.data[index] ?? 255;
const green = pixels.data[index + 1] ?? 255;
@@ -235,9 +248,6 @@ test.describe("offline font diagnostic", () => {
const preview = document.querySelector('[aria-hidden="false"] canvas');
return {
rasterDataUrl: raster.toDataURL(),
- textLayerMarkers: Object.fromEntries(
- markers.map((sample) => [sample.name, textLayer.includes(sample.marker)]),
- ),
referenceGlyphs: measure(raster, rasterScale),
previewGlyphs: preview ? measure(preview, preview.width / baseViewport.width) : [],
};
@@ -245,8 +255,9 @@ test.describe("offline font diagnostic", () => {
await loadingTask.destroy();
}
},
- { bytes: Array.from(bytes), markers: offlineFontScriptSamples },
+ { bytes: Array.from(bytes), boxes },
);
+ return { ...rasterEvidence, textLayerMarkers };
}
async function report(testInfo: TestInfo, name: string, reportData: Record) {
@@ -335,6 +346,7 @@ test.describe("offline font diagnostic", () => {
let markerResultValue: PdfMarkers | null = null;
let rasterEvidence: PdfRasterEvidence | null = null;
let downloadStatus = "not-started";
+ let rasterEvidenceStatus = "not-attempted";
try {
await page.goto(fixture.builderURL);
await openSidebarSection(page, "Export");
@@ -346,26 +358,35 @@ test.describe("offline font diagnostic", () => {
const path = testInfo.outputPath("offline-font-browser-download.pdf");
await download.saveAs(path);
const bytes = new Uint8Array(await readFile(path));
- markerResultValue = extractedMarkerResult(await readPdfText(bytes));
- rasterEvidence = await renderPdfRasterEvidence(page, bytes);
- await testInfo.attach("browser-download-raster.png", {
- body: Buffer.from(rasterEvidence.rasterDataUrl.split(",")[1] ?? "", "base64"),
- contentType: "image/png",
- });
+ try {
+ markerResultValue = extractedMarkerResult(await readPdfText(bytes));
+ rasterEvidence = await renderPdfRasterEvidence(page, bytes);
+ await testInfo.attach("browser-download-raster.png", {
+ body: Buffer.from(rasterEvidence.rasterDataUrl.split(",")[1] ?? "", "base64"),
+ contentType: "image/png",
+ });
+ rasterEvidenceStatus = "received";
+ } catch {
+ rasterEvidenceStatus = "unresolved-raster-evidence-error";
+ }
} catch {
- downloadStatus = "renderer-or-network-error";
+ downloadStatus = "download-error";
} finally {
await report(testInfo, "browser-download", {
cache: "new-browser-context",
blockedExternalFontRequests: cold.blockedRequests,
networkStatus: networkStatus(cold.blockedRequests),
downloadStatus,
+ rasterEvidenceStatus,
textLayerMarkers: markerResultValue ?? "not-extracted",
glyphStatus: rasterEvidence?.referenceGlyphs ?? "not-rasterized",
});
await cold.context.close();
}
- if (rasterEvidence) {
+ if (downloadStatus === "received") {
+ expect(rasterEvidenceStatus).toBe("received");
+ expect(rasterEvidence).not.toBeNull();
+ if (!rasterEvidence) return;
expect(rasterEvidence.referenceGlyphs).toHaveLength(offlineFontScriptSamples.length);
expect(rasterEvidence.referenceGlyphs.every(({ status }) => status === "visible")).toBe(true);
}
From 313cfab6316c2d8226c49c9272153bed919c6cd6 Mon Sep 17 00:00:00 2001
From: Amruth Pillai
Date: Sun, 6 Sep 2026 06:00:59 +0200
Subject: [PATCH 4/4] test: close offline font diagnostic review gaps
---
tests/e2e/fixtures/offline-font-diagnostic.ts | 5 ++
.../e2e/fixtures/offline-font-markers.test.ts | 31 ++++++++++-
tests/e2e/fixtures/offline-font-markers.ts | 19 +++++++
tests/e2e/specs/offline-fonts.spec.ts | 54 +++++++++++--------
4 files changed, 86 insertions(+), 23 deletions(-)
create mode 100644 tests/e2e/fixtures/offline-font-diagnostic.ts
diff --git a/tests/e2e/fixtures/offline-font-diagnostic.ts b/tests/e2e/fixtures/offline-font-diagnostic.ts
new file mode 100644
index 000000000..04a94f711
--- /dev/null
+++ b/tests/e2e/fixtures/offline-font-diagnostic.ts
@@ -0,0 +1,5 @@
+export function assertPdfDownloadReceived(downloadStatus: string): asserts downloadStatus is "received" {
+ if (downloadStatus !== "received") {
+ throw new Error(`PDF download diagnostic did not receive a download: ${downloadStatus}`);
+ }
+}
diff --git a/tests/e2e/fixtures/offline-font-markers.test.ts b/tests/e2e/fixtures/offline-font-markers.test.ts
index 3a0462dd5..c10611337 100644
--- a/tests/e2e/fixtures/offline-font-markers.test.ts
+++ b/tests/e2e/fixtures/offline-font-markers.test.ts
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
-import { classifyRasterInk, locatePdfMarkerBoxes } from "./offline-font-markers";
+import { assertPdfDownloadReceived } from "./offline-font-diagnostic";
+import { classifyRasterInk, classifyRasterMeasurements, locatePdfMarkerBoxes } from "./offline-font-markers";
const marker = { name: "cjk", marker: "简体中文" } as const;
@@ -41,4 +42,32 @@ describe("classifyRasterInk", () => {
"tofu-like",
);
});
+
+ it("classifies raw browser measurements through shared raster classifier", () => {
+ expect(
+ classifyRasterMeasurements([
+ { name: "blank", located: true, inkPixels: 0, interiorInk: 0, trimmedWidth: 0, trimmedHeight: 0 },
+ { name: "tofu", located: true, inkPixels: 100, interiorInk: 0, trimmedWidth: 20, trimmedHeight: 20 },
+ { name: "visible", located: true, inkPixels: 100, interiorInk: 20, trimmedWidth: 20, trimmedHeight: 20 },
+ { name: "missing", located: false, inkPixels: 0, interiorInk: 0, trimmedWidth: 0, trimmedHeight: 0 },
+ ]),
+ ).toEqual([
+ { name: "blank", status: "blank", inkPixels: 0, trimmedWidth: 0, trimmedHeight: 0 },
+ { name: "tofu", status: "tofu-like", inkPixels: 100, trimmedWidth: 20, trimmedHeight: 20 },
+ { name: "visible", status: "visible", inkPixels: 100, trimmedWidth: 20, trimmedHeight: 20 },
+ { name: "missing", status: "not-located", inkPixels: 0, trimmedWidth: 0, trimmedHeight: 0 },
+ ]);
+ });
+});
+
+describe("assertPdfDownloadReceived", () => {
+ it("fails diagnostic when browser PDF download errors", () => {
+ expect(() => assertPdfDownloadReceived("download-error")).toThrow(
+ "PDF download diagnostic did not receive a download",
+ );
+ });
+
+ it("accepts received browser PDF download", () => {
+ expect(() => assertPdfDownloadReceived("received")).not.toThrow();
+ });
});
diff --git a/tests/e2e/fixtures/offline-font-markers.ts b/tests/e2e/fixtures/offline-font-markers.ts
index a1d6037e4..0a956e89f 100644
--- a/tests/e2e/fixtures/offline-font-markers.ts
+++ b/tests/e2e/fixtures/offline-font-markers.ts
@@ -31,6 +31,16 @@ export type RasterInkMeasurements = {
export type RasterGlyphStatus = "visible" | "blank" | "tofu-like";
+export type RasterGlyphMeasurement = RasterInkMeasurements & {
+ name: TName;
+ located: boolean;
+};
+
+export type RasterGlyphEvidence = Omit & {
+ name: TName;
+ status: RasterGlyphStatus | "not-located";
+};
+
type TextItemRange = PdfTextItem & {
start: number;
end: number;
@@ -112,3 +122,12 @@ export const classifyRasterInk = ({
const interiorRatio = interiorInk / Math.max(inkPixels, 1);
return interiorRatio < 0.08 && trimmedWidth >= 8 && trimmedHeight >= 8 ? "tofu-like" : "visible";
};
+
+export const classifyRasterMeasurements = (
+ measurements: readonly RasterGlyphMeasurement[],
+): RasterGlyphEvidence[] =>
+ measurements.map(({ name, located, interiorInk, ...measurements }) => ({
+ ...measurements,
+ name,
+ status: located ? classifyRasterInk({ ...measurements, interiorInk }) : "not-located",
+ }));
diff --git a/tests/e2e/specs/offline-fonts.spec.ts b/tests/e2e/specs/offline-fonts.spec.ts
index faec317e5..b92661207 100644
--- a/tests/e2e/specs/offline-fonts.spec.ts
+++ b/tests/e2e/specs/offline-fonts.spec.ts
@@ -1,8 +1,13 @@
import type { Browser, BrowserContext, Page, TestInfo } from "@playwright/test";
+import type {
+ RasterGlyphEvidence as MarkerRasterGlyphEvidence,
+ RasterGlyphMeasurement as MarkerRasterGlyphMeasurement,
+} from "../fixtures/offline-font-markers";
import { readFile, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
-import { locatePdfMarkerBoxes } from "../fixtures/offline-font-markers";
+import { assertPdfDownloadReceived } from "../fixtures/offline-font-diagnostic";
+import { classifyRasterMeasurements, locatePdfMarkerBoxes } from "../fixtures/offline-font-markers";
import { offlineFontScriptSamples, seedOfflineFontResume } from "../fixtures/offline-fonts";
import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
import { expect, test } from "../fixtures/test";
@@ -22,13 +27,8 @@ type ColdContext = {
type PdfMarkers = Record<(typeof offlineFontScriptSamples)[number]["name"], boolean>;
-type RasterGlyphEvidence = {
- name: (typeof offlineFontScriptSamples)[number]["name"];
- status: "visible" | "blank" | "tofu-like" | "not-located";
- inkPixels: number;
- trimmedWidth: number;
- trimmedHeight: number;
-};
+type RasterGlyphEvidence = MarkerRasterGlyphEvidence<(typeof offlineFontScriptSamples)[number]["name"]>;
+type RasterGlyphMeasurement = MarkerRasterGlyphMeasurement<(typeof offlineFontScriptSamples)[number]["name"]>;
type PdfRasterEvidence = {
rasterDataUrl: string;
@@ -177,12 +177,20 @@ test.describe("offline font diagnostic", () => {
background: "white",
}).promise;
- function measure(canvas: HTMLCanvasElement, scale: number): RasterGlyphEvidence[] {
+ function measure(canvas: HTMLCanvasElement, scale: number): RasterGlyphMeasurement[] {
const context = canvas.getContext("2d");
if (!context) throw new Error("Missing PDF preview raster context.");
const pixels = context.getImageData(0, 0, canvas.width, canvas.height);
return boxes.map(({ name, box }) => {
- if (!box) return { name, status: "not-located", inkPixels: 0, trimmedWidth: 0, trimmedHeight: 0 };
+ if (!box)
+ return {
+ name,
+ located: false,
+ inkPixels: 0,
+ interiorInk: 0,
+ trimmedWidth: 0,
+ trimmedHeight: 0,
+ };
const markerLeft = box.x * scale;
const markerTop = box.y * scale;
const markerRight = (box.x + box.width) * scale;
@@ -215,7 +223,7 @@ test.describe("offline font diagnostic", () => {
}
}
if (inkPixels === 0) {
- return { name, status: "blank", inkPixels, trimmedWidth: 0, trimmedHeight: 0 };
+ return { name, located: true, inkPixels, interiorInk: 0, trimmedWidth: 0, trimmedHeight: 0 };
}
let interiorInk = 0;
for (
@@ -239,9 +247,7 @@ test.describe("offline font diagnostic", () => {
}
const trimmedWidth = maxX - minX + 1;
const trimmedHeight = maxY - minY + 1;
- const interiorRatio = interiorInk / Math.max(inkPixels, 1);
- const status = interiorRatio < 0.08 && trimmedWidth >= 8 && trimmedHeight >= 8 ? "tofu-like" : "visible";
- return { name, status, inkPixels, trimmedWidth, trimmedHeight };
+ return { name, located: true, inkPixels, interiorInk, trimmedWidth, trimmedHeight };
});
}
@@ -257,7 +263,12 @@ test.describe("offline font diagnostic", () => {
},
{ bytes: Array.from(bytes), boxes },
);
- return { ...rasterEvidence, textLayerMarkers };
+ return {
+ ...rasterEvidence,
+ referenceGlyphs: classifyRasterMeasurements(rasterEvidence.referenceGlyphs),
+ previewGlyphs: classifyRasterMeasurements(rasterEvidence.previewGlyphs),
+ textLayerMarkers,
+ };
}
async function report(testInfo: TestInfo, name: string, reportData: Record) {
@@ -383,13 +394,12 @@ test.describe("offline font diagnostic", () => {
});
await cold.context.close();
}
- if (downloadStatus === "received") {
- expect(rasterEvidenceStatus).toBe("received");
- expect(rasterEvidence).not.toBeNull();
- if (!rasterEvidence) return;
- expect(rasterEvidence.referenceGlyphs).toHaveLength(offlineFontScriptSamples.length);
- expect(rasterEvidence.referenceGlyphs.every(({ status }) => status === "visible")).toBe(true);
- }
+ assertPdfDownloadReceived(downloadStatus);
+ expect(rasterEvidenceStatus).toBe("received");
+ expect(rasterEvidence).not.toBeNull();
+ if (!rasterEvidence) return;
+ expect(rasterEvidence.referenceGlyphs).toHaveLength(offlineFontScriptSamples.length);
+ expect(rasterEvidence.referenceGlyphs.every(({ status }) => status === "visible")).toBe(true);
});
test("exercises restarted-server PDF and records server observability boundary", async ({