Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f56cd83
perf: rolepatch LCP pass 3 (home, pricing, tools), restore Clarity in…
sarthakagrawal927 Oct 11, 2026
ecb8c42
perf: rolepatch pricing/tools LCP round 2 (CSS inlining, optional fon…
sarthakagrawal927 Oct 11, 2026
adb1be6
Merge remote-tracking branch 'origin/main' into codex/lcp-pass3
sarthakagrawal927 Oct 13, 2026
a2beead
Make guest onboarding resume-first and isolate public route bundles
sarthakagrawal927 Oct 13, 2026
6414f47
Keep performance helpers within complexity limits and browser artifac…
sarthakagrawal927 Oct 13, 2026
49d10d3
Separate deferred monitoring scheduling from event reporting
sarthakagrawal927 Oct 13, 2026
ec97a7c
Render the shared app mark as a monochrome icon
sarthakagrawal927 Oct 13, 2026
69cdf0e
Keep anonymous inline-CSS HTML in standards mode
sarthakagrawal927 Oct 13, 2026
edb6fce
Share the monochrome product mark across application navigation
sarthakagrawal927 Oct 13, 2026
2a42774
Record local UX and performance evidence with open release gates
sarthakagrawal927 Oct 13, 2026
6cfe5a4
Reduce public heading font transfer and avoid speculative tool bundles
sarthakagrawal927 Oct 13, 2026
fcf84ba
Negotiate byte-preserving Brotli for public pricing and tools documents
sarthakagrawal927 Oct 13, 2026
f8c3db4
Revert "Negotiate byte-preserving Brotli for public pricing and tools…
sarthakagrawal927 Oct 13, 2026
56eca5c
Use the standard Latin heading subset with complete-face fallback
sarthakagrawal927 Oct 13, 2026
ad33711
Scope Next utility generation to its complete application source
sarthakagrawal927 Oct 13, 2026
9033f64
Reduce public document styles and load account UI only for signed-in …
sarthakagrawal927 Oct 13, 2026
8f99256
Retain native Next async scheduling alongside public dependency reduc…
sarthakagrawal927 Oct 13, 2026
3cf2f93
Restore the unchanged document helper bytes
sarthakagrawal927 Oct 13, 2026
d7414e1
Load complete application utilities outside the public catalog
sarthakagrawal927 Oct 13, 2026
7f494a5
fix: preserve workspace palette in lazy account UI
sarthakagrawal927 Oct 13, 2026
0703989
refactor: use shared workspace utility aliases
sarthakagrawal927 Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions __tests__/fixtures/resume-import/synthetic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Ada Example

ada@example.invalid

## Experience

### Example Company

- Built accessible TypeScript screens.

## Projects

### Example Reader

- Released an accessible reading application.

## Education

BSc, Example University.

## Skills

TypeScript, React.
26 changes: 26 additions & 0 deletions __tests__/html-document.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// @vitest-environment node
import { describe, expect, it } from 'vitest';
import { ensureLeadingHtmlDoctype } from '../html-document.mjs';

const encoder = new TextEncoder();
const decode = (body: Uint8Array) => new TextDecoder().decode(body);

describe('anonymous cached HTML standards mode', () => {
it('moves the doctype ahead of Next inlineCss polyfills without losing script or content', () => {
const script = '<script src="/_next/static/chunks/polyfills.js" noModule=""></script>';
const document = '<!DOCTYPE html><html><head></head><body>Résumé</body></html>';
const result = decode(ensureLeadingHtmlDoctype(encoder.encode(script + document)));
expect(result).toBe(`<!DOCTYPE html>${script}${document.slice(15)}`);
expect(result.match(/<!DOCTYPE html>/g)).toHaveLength(1);
});
it('preserves normal documents and does not invent a doctype or move arbitrary content', () => {
for (const html of [
'<!DOCTYPE html><html><body>Normal</body></html>',
'<html><body>No declaration</body></html>',
'<p>Other prefix</p><!DOCTYPE html><html></html>',
]) {
const original = encoder.encode(html);
expect(ensureLeadingHtmlDoctype(original)).toBe(original);
}
});
});
51 changes: 51 additions & 0 deletions docs/architecture/decisions/0016-public-document-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: Public document rendering
---

# ADR-0016: Public document rendering

Date: 2026-10-13
Status: Accepted for the source candidate; no production release

## Context

Pricing and the tools catalog have server-rendered text, but their initial
document also carried the complete application stylesheet twice: in the HTML
and in Next's Flight payload. Guest navigation imported the authenticated
dropdown and sidebar primitives even when there was no signed-in account.
These costs compete with the public text rendering path tracked in
[issue #39](https://github.com/Significant-Hobbies/rolepatch/issues/39).

## Decision

Use Next's documented external-stylesheet mode. Keep the complete stylesheets
render-blocking, and retain the existing critical-CSS build step for
prerendered files. Import workspace-only styles from the already lazy workspace
interior. Generate eager utilities from catalog and shared navigation sources;
the complete application utility set loads on other routes and with the
authenticated menu. Their rules and the public content remain unchanged.

Load the signed-in menu through `next/dynamic` after a session supplies a user.
Keep the existing guest sign-in path and all authenticated menu actions. A
disabled account placeholder reserves space while the account module loads;
it exposes neither settings nor sign-out until those actions are ready.

Retain Next's native async script scheduling, inline Flight data and Worker
cache/auth boundaries. There are no timers or interaction-based hydration gates.

## Consequences

The public document avoids duplicated inline CSS and guest-only visits avoid
account UI code. External CSS adds a request waterfall, so production-build
Lighthouse comparisons must verify the trade-off. Menu, tool and builder
readiness need interaction assertions, not just screenshots or static HTML.
Responsive geometry and workspace theme portals must remain consistent.

## Alternatives considered

- Global `inlineCss`: Next documents its initial Flight duplication; keeping
the full stylesheet inline preserves the measured document overhead.
- Deferring every route or removing hydration: rejected because workspace,
account and mutation flows need the existing Next runtime.
- Native `defer`: did not consistently meet the gate and makes hydration wait
for stylesheets; keep the framework's original scheduling.
112 changes: 112 additions & 0 deletions docs/operations/evidence/resume-first-performance-2026-10-13.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
{
"date": "2026-10-13",
"repository": "Significant-Hobbies/rolepatch",
"issues": [
32,
39
],
"pull_request": 34,
"verified_remote_main": "eb355d08d5df1a9bec92ef8f77511cb216b10912",
"unchanged_remote_pr_head": "ecb8c423aba3722c71a6c63855df6099ddc1bdeb",
"measured_code_sha": "edb6fce51e51190f40e58f8573ad4f55f7d153ed",
"history": "Current main merged into retained PR34 branch; both original author commits remain ancestors. No rebases or force pushes.",
"runtime": "wrangler dev --local --env cache; full cf:build; anonymous public routes only",
"workload": "Lighthouse 12.8.2 mobile defaults; simulated throttling; cold browser storage each run; sequential 3 runs per route; external HTTPS blocked to prevent outbound provider/analytics traffic",
"baseline_note": "Retained production build at verified main; source branch changed during baseline but built Worker artifacts remained main.",
"main": {
"/": {
"median_lcp_ms": 1600.5108,
"median_performance": 100,
"best_practices": [
100,
100,
100
]
},
"/pricing": {
"median_lcp_ms": 3637.28025,
"median_performance": 89.0,
"best_practices": [
100,
100,
100
]
},
"/tools": {
"median_lcp_ms": 4421.040149999999,
"median_performance": 83.0,
"best_practices": [
100,
100,
100
]
}
},
"candidate": {
"/": {
"median_lcp_ms": 1740.6149,
"median_performance": 100,
"best_practices": [
100,
100,
100
]
},
"/pricing": {
"median_lcp_ms": 2662.948400000001,
"median_performance": 95.0,
"best_practices": [
100,
100,
100
]
},
"/tools": {
"median_lcp_ms": 2920.2155999999995,
"median_performance": 95.0,
"best_practices": [
100,
100,
100
]
}
},
"checks": {
"quality": "passed: Biome, types, coverage, docs, extension types, unused, complexity, duplication, cycles, dependencies, suppressions, hygiene",
"unit_passed": 762,
"unit_skipped": 1,
"focused_browser_passed": 24,
"responsive_widths": [
390,
768,
1440
],
"responsive_routes": [
"/",
"/pricing",
"/tools",
"/resume-builder",
"/jobs"
],
"production_build": "complete cf:build passed"
},
"lcp_gate_ms": 2500,
"lcp_gate_met": false,
"best_practices_preserved": true,
"publication": {
"status": "blocked",
"git_push": "HTTP 403: Permission denied to configured account",
"github_connector": "HTTP 403: Resource not accessible by integration creating a tree",
"remote_ci": "No new run possible because code could not be pushed; old PR checks still fail. Local required checks pass.",
"pr_metadata": "HTTP 403: Resource not accessible by integration updating PR body; PR metadata remains unchanged."
},
"open_gates": [
"Any unmet local LCP medians",
"GitHub write access, actual branch push, and fresh remote CI",
"Human/editorial/taste acceptance",
"Real-device acceptance",
"Production acceptance, including reported Cloudflare 401 noise",
"PDF/DOCX import end-to-end AI structuring and real OAuth #12 were excluded"
],
"boundaries": "Local synthetic guest checks only. No credentials inspected or changed; no owner resumes/research/prepurge read; no live providers, paid services, deploys, releases, merges, production configuration or remote migrations. No skipped or synthetic checks claimed as live acceptance."
}
10 changes: 10 additions & 0 deletions e2e/navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ test.describe('public pages load', () => {
test('landing page renders', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/RolePatch|Resume/i);
await page
.getByRole('link', { name: /Tailor my resume/i })
.first()
.click();
await expect(page).toHaveURL(/resume-builder/);
await expect(page.getByRole('button', { name: 'Import Resume', exact: true })).toBeEnabled();
});

test('dashboard is reachable in guest mode', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.getByRole('button', { name: /create|new resume/i })).toBeVisible();
await expect(page.getByRole('button', { name: 'Import Resume', exact: true })).toBeEnabled();
await expect(page.getByRole('button', { name: '+ Add Job', exact: true })).toBeDisabled();
});

test('pricing page renders', async ({ page }) => {
await page.goto('/pricing');
expect(await page.evaluate(() => document.compatMode)).toBe('CSS1Compat');
await expect(page.getByText(/Starter|Pro|Bulk/i).first()).toBeVisible();
});

Expand All @@ -25,6 +34,7 @@ test.describe('public pages load', () => {

test('tools index links to diff and keywords', async ({ page }) => {
await page.goto('/tools');
expect(await page.evaluate(() => document.compatMode)).toBe('CSS1Compat');
await expect(page.getByRole('link', { name: /diff/i })).toBeVisible();
await expect(page.getByRole('link', { name: /keyword/i })).toBeVisible();
});
Expand Down
93 changes: 93 additions & 0 deletions e2e/public-performance.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { expect, test } from '@playwright/test';

test('account menu becomes usable after a synthetic session loads its account-only code', async ({
page,
}) => {
await page.emulateMedia({ colorScheme: 'light' });
await page.addInitScript(() => {
localStorage.setItem('rolepatch-workspace-theme', 'light');
});
const synthetic = {
user: {
id: 'public-performance-fixture',
name: 'Sample Reader',
email: 'sample@example.org',
emailVerified: false,
createdAt: '2026-01-01T00:00:00.000Z',
updatedAt: '2026-01-01T00:00:00.000Z',
},
session: {
id: 'fixture-session',
token: 'synthetic-local-only',
userId: 'public-performance-fixture',
expiresAt: '2030-01-01T00:00:00.000Z',
createdAt: '2026-01-01T00:00:00.000Z',
updatedAt: '2026-01-01T00:00:00.000Z',
},
};
await page.route('**/api/auth/get-session**', (route) => route.fulfill({ json: synthetic }));
for (const path of ['/pricing', '/resume-builder']) {
await page.goto(path);
if (path === '/resume-builder' && (page.viewportSize()?.width ?? 0) < 768) {
await page.getByRole('button', { name: 'Toggle Sidebar', exact: true }).click();
}
if (path === '/resume-builder') {
await expect(page.locator('[data-sidebar="sidebar"]')).toHaveCSS(
'background-color',
'oklch(0.985 0 0)'
);
}
const account = page.getByRole('button', { name: 'Account menu', exact: true });
await expect(account).toBeEnabled();
await account.click();
await expect(page.getByRole('menu')).toHaveCSS('background-color', 'oklch(1 0 0)');
await expect(page.getByRole('menuitem', { name: 'Account settings' })).toHaveAttribute(
'href',
'/settings'
);
await expect(page.getByRole('menuitem', { name: 'Sign out' })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /pricing|buy|billing/i })).toHaveCount(0);
await page.keyboard.press('Escape');
}
});

for (const route of ['/pricing', '/tools']) {
test(`${route} hydrates its guest controls without losing server content`, async ({ page }) => {
const errors: string[] = [];
page.on('pageerror', (error) => errors.push(error.message));
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(route);
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
await expect(page.getByRole('button', { name: 'Sign in', exact: true })).toBeEnabled();
await page.getByRole('button', { name: 'Open menu' }).click();
await expect(page.getByRole('button', { name: 'Close menu' })).toHaveAttribute(
'aria-expanded',
'true'
);
await page.getByRole('button', { name: 'Close menu' }).click();
await expect(page.getByRole('button', { name: 'Open menu' })).toHaveAttribute(
'aria-expanded',
'false'
);
expect(errors).toEqual([]);
});
}

test('tools keeps all cards and loads the selected tool on navigation', async ({ page }) => {
const prefetched: string[] = [];
page.on('request', (request) => {
if (
request.headers()['next-router-prefetch'] &&
new URL(request.url()).pathname.startsWith('/tools/')
)
prefetched.push(request.url());
});
await page.goto('/tools');
await expect(page.getByRole('link', { name: /Use tool/ })).toHaveCount(6);
await page.waitForLoadState('networkidle');
expect(prefetched).toEqual([]);
await page.getByRole('link', { name: /ATS Keyword Checker/ }).click();
await expect(page).toHaveURL(/\/tools\/keywords$/);
await expect(page.locator('textarea').first()).toBeVisible();
await expect(page.locator('textarea').nth(1)).toBeVisible();
});
45 changes: 45 additions & 0 deletions e2e/resume-first.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect, test } from '@playwright/test';
import { resolve } from 'node:path';

test('guest import unlocks Add Job only after a ready master survives reload', async ({ page }) => {
await page.goto('/jobs');
await expect(page.getByRole('button', { name: '+ Add Job', exact: true })).toBeDisabled();
await page.getByRole('button', { name: '+ New Resume', exact: true }).click();
await page.getByRole('button', { name: 'Save draft', exact: true }).click();
await expect(page).toHaveURL(/resume-builder\?resume=/);
await page.goto('/jobs');
await expect(page.getByRole('button', { name: '+ Add Job', exact: true })).toBeDisabled();
await page.goto('/resume-builder');
await expect(page.getByRole('button', { name: 'Import Resume', exact: true })).toBeEnabled();
await page
.locator('input[type=file]')
.setInputFiles(resolve('__tests__/fixtures/resume-import/synthetic.md'));
await expect(page.getByText('Master resume · ready for a job', { exact: true })).toBeVisible();
await page.reload();
await expect(page.getByText('Master resume · ready for a job', { exact: true })).toBeVisible();
await page.goto('/jobs');
await expect(page.getByRole('button', { name: '+ Add Job', exact: true })).toBeEnabled();
await page.getByRole('button', { name: '+ Add Job', exact: true }).click();
await expect(page.getByRole('combobox')).toContainText('synthetic');
});

test('public and workspace routes fit established responsive layouts', async ({
page,
}, testInfo) => {
for (const route of ['/', '/pricing', '/tools', '/resume-builder', '/jobs']) {
await page.goto(route);
await expect(page.locator('main')).toBeVisible();
if (route === '/resume-builder')
await expect(page.getByRole('button', { name: 'Import Resume', exact: true })).toBeEnabled();
for (const width of [390, 768, 1440]) {
await page.setViewportSize({ width, height: 900 });
await expect
.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth))
.toBe(true);
await page.screenshot({
path: testInfo.outputPath(`${route.slice(1) || 'home'}-${width}.png`),
fullPage: true,
});
}
}
});
Loading
Loading