Files
zeekayandHanzo Dev 18c16ecf2c Zen is the face, and the check can prove it
The site rendered in the system fallback: @hanzo/design 0.5.x replaced Geist
with Zen, and this app pinned 0.4.6, so the family every surface asked for was
one nothing declared any more. Bumping design alone strands it — @hanzo/ui
inlines a copy of that token block — so the pair moves together, to
@hanzo/design 0.5.21 and @hanzo/ui 8.3.4.

@hanzo/ui depends on none of its @hanzogui/* peers; it declares them, so this
app installs them. With only @hanzo/gui declared, pnpm resolved
@hanzogui/lucide-icons-2 a generation behind, which dragged a second
@hanzogui/core, and every @hanzo/ui component carrying an icon then read a
theme context no provider fills — prerender died on "Missing theme.". Declaring
the cohort, as hanzo.ai and the console already do, collapses it to one core.

The old font test matched /geist|system-ui|sans-serif/, so it passed on the
fallback: the exact failure it existed to catch. Both checks ask the font
loader now. shots.mjs asks for Zen Mono only where a page renders mono, because
a face loads when a glyph wants it; mutation-checked by aborting **/*.woff2.

CommandPalette was controlled, nothing ever owned it, and its global handler
could only close — so Cmd+K opened nothing and its test had never passed. A
palette is the thing Cmd+K opens, so it owns the shortcut and mounts itself.

51 pages, 414/414 gui classes covered, 36/36 e2e, 60/60 routes across
phone+desktop x light+dark.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-24 09:51:17 -07:00

132 lines
4.8 KiB
TypeScript

import { test, expect } from '@playwright/test';
// List of all pages to test
const pages = [
{ path: '/', name: 'Home' },
{ path: '/about', name: 'About' },
{ path: '/team', name: 'Team' },
{ path: '/pricing', name: 'Pricing' },
{ path: '/solutions', name: 'Solutions' },
{ path: '/capabilities', name: 'Capabilities' },
{ path: '/case-studies', name: 'Case Studies' },
{ path: '/examples', name: 'Examples' },
{ path: '/research', name: 'Research' },
{ path: '/press', name: 'Press' },
{ path: '/models', name: 'AI Models' },
{ path: '/help', name: 'Help' },
{ path: '/contact', name: 'Contact' },
{ path: '/services', name: 'Services' },
{ path: '/status', name: 'Status' },
{ path: '/security', name: 'Security' },
{ path: '/terms', name: 'Terms' },
{ path: '/privacy', name: 'Privacy' },
{ path: '/careers', name: 'Careers' },
// Product pages
{ path: '/products/zen', name: 'Zen' },
{ path: '/products/koan', name: 'Koan' },
{ path: '/products/hanzo-ai', name: 'Hanzo AI' },
{ path: '/products/hanzo-dx', name: 'Hanzo DX' },
{ path: '/products/hanzo-ml', name: 'Hanzo ML' },
{ path: '/products/hanzo-dev', name: 'Hanzo Dev' },
{ path: '/products/hanzo-team', name: 'Hanzo Team' },
{ path: '/products/lux', name: 'Lux' },
];
test.describe('All pages render correctly', () => {
for (const page of pages) {
test(`${page.name} page (${page.path}) renders without errors`, async ({ page: browserPage }) => {
// Track console errors
const consoleErrors: string[] = [];
browserPage.on('console', msg => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Navigate to page
const response = await browserPage.goto(page.path, { waitUntil: 'networkidle' });
// Check response is successful
expect(response?.status()).toBeLessThan(400);
// Check page has content (not blank)
const bodyContent = await browserPage.locator('body').textContent();
expect(bodyContent?.length).toBeGreaterThan(100);
// Check no font loading errors (the main issue we're fixing)
const fontErrors = consoleErrors.filter(err =>
err.includes('font') || err.includes('MIME type') || err.includes('Cal Sans')
);
expect(fontErrors).toHaveLength(0);
// Check navbar is visible
await expect(browserPage.locator('nav').first()).toBeVisible();
// Check footer is visible (most pages have footer)
const footer = browserPage.locator('footer');
if (await footer.count() > 0) {
await expect(footer.first()).toBeVisible();
}
});
}
});
test.describe('Research page hash navigation', () => {
const hashRoutes = [
{ hash: '#ai', expectedFilter: 'ai' },
{ hash: '#consensus', expectedFilter: 'consensus' },
{ hash: '#fhe', expectedFilter: 'fhe' },
{ hash: '#mpc', expectedFilter: 'mpc' },
{ hash: '#papers', expectedSection: 'papers' },
];
for (const route of hashRoutes) {
test(`Research page with ${route.hash} works correctly`, async ({ page }) => {
const response = await page.goto(`/research${route.hash}`, { waitUntil: 'networkidle' });
// Page should load
expect(response?.status()).toBeLessThan(400);
// Should have papers section
await expect(page.locator('#papers')).toBeVisible();
});
}
});
test('Command palette opens with Cmd+K', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' });
// Press Cmd+K
await page.keyboard.press('Meta+k');
// Command palette should be visible
await expect(page.getByPlaceholder('Search pages, products, docs...')).toBeVisible();
// Press Escape to close
await page.keyboard.press('Escape');
// Should close
await expect(page.getByPlaceholder('Search pages, products, docs...')).not.toBeVisible();
});
test('Zen is the face, and it actually loaded', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' });
await page.evaluate(() => document.fonts.ready);
// The computed family and the loaded FILE are different facts, and the old
// version of this test only asked the first one — permissively enough
// (`/geist|system-ui|sans-serif/`) that it passed on the system fallback,
// which is the exact failure it was written to catch.
const seen = await page.evaluate(() => ({
body: getComputedStyle(document.body).fontFamily,
heading: getComputedStyle(document.querySelector('h1') ?? document.body).fontFamily,
loaded: document.fonts.check('16px Zen'),
mono: document.fonts.check('16px "Zen Mono"'),
}));
expect(seen.loaded, 'Zen did not load — the page is on the fallback face').toBe(true);
expect(seen.mono, 'Zen Mono did not load').toBe(true);
expect(seen.body.toLowerCase()).toContain('zen');
expect(seen.heading.toLowerCase()).toContain('zen');
});