mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
The landing drew Smarter, Faster, Cheaper and the plans only once their reads answered: the whole research record (1.2 MB), the price list, the model list and the plans, about 160 KB compressed, asked for after hydration. On a slow phone the figures appeared some 45 s after the page. The build now reads the slices those figures need and derives them with the same functions the browser runs (lib/figures, scripts/figures.ts), one file a figure under lib/data/figures. The hooks draw that from the first render, so the export holds every figure and no "Reading" line, then read the slices again once the page is idle and move a figure only where the answer differs (lib/api/use useLive). A figure the record no longer holds comes down; a read that fails keeps the build's. The slices: one research read a board benchmark with its all and vs-claude-code measures, the timing runs' p50s, the price-list rows the boards join (by id, and by each system's name), the model list's rows for Kai, the frontier LLMs and the LLM its call is timed beside, and the plans. Lists are walked by their next cursor. Kai's speed and spend (lib/home call, spend) read those two slices, so the landing never reads the whole record or the whole model list. e2e/unit/seed.spec.ts holds each slice to the whole read it replaced, and the gates serve and narrow every read as the API does and wait for the page's live reads before they look for a served figure.
83 lines
4.2 KiB
TypeScript
83 lines
4.2 KiB
TypeScript
// Every icon the benchmark pages draw is a real one, drawn.
|
||
//
|
||
// An icon that renders at 0×0, or as an <svg> with nothing in it, is a gap the
|
||
// reader sees as a broken page and no other gate notices. An arrow typed as a
|
||
// character is not an icon either: its shape is whatever the visitor's fonts
|
||
// make of it, so the board uses one set (gui's lucide) and types none. Checked on
|
||
// the index, a benchmark's own page and the open palette, in both schemes, on a
|
||
// phone and a laptop: every visible <svg> has a size and a shape, every icon
|
||
// (an <svg> that is not a chart) sits between 12 and 24 px, and no arrow glyph
|
||
// stands in for one.
|
||
|
||
import { test, expect, type Page } from '@playwright/test'
|
||
import { serveExport } from './export'
|
||
import { api, listing } from './revision'
|
||
import snapshot from '../../lib/data/benchmarks-kai.json' with { type: 'json' }
|
||
|
||
let site: Awaited<ReturnType<typeof serveExport>>
|
||
test.beforeAll(async () => {
|
||
site = await serveExport()
|
||
})
|
||
test.afterAll(async () => {
|
||
await site.close()
|
||
})
|
||
|
||
/** Every visible <svg>: where it is, its box, how many shapes it holds, and whether it is a chart. */
|
||
const svgs = (page: Page) =>
|
||
page.evaluate(() =>
|
||
[...document.querySelectorAll('svg')]
|
||
.filter((el) => el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }))
|
||
.map((el) => {
|
||
const r = el.getBoundingClientRect()
|
||
const chart = el.getAttribute('role') === 'img' && Boolean(el.getAttribute('aria-label'))
|
||
const where = el.closest('header') ? 'header' : el.closest('footer') ? 'footer' : el.closest('[role="dialog"]') ? 'palette' : 'page'
|
||
const name = el.closest('[aria-label]')?.getAttribute('aria-label') ?? el.parentElement?.textContent?.trim().slice(0, 32) ?? ''
|
||
return { where, name, w: Math.round(r.width), h: Math.round(r.height), shapes: el.querySelectorAll('path, circle, rect, line, polyline, polygon, ellipse, use').length, chart }
|
||
}),
|
||
)
|
||
|
||
/** Arrow characters used as icons inside the page's own content. */
|
||
const ARROWS = /[←→↑↓↗↘⟶➜➔]/
|
||
|
||
const check = (all: Awaited<ReturnType<typeof svgs>>, at: string) => {
|
||
expect(all.length, `${at}: no icon drawn at all`).toBeGreaterThan(0)
|
||
for (const s of all) {
|
||
expect(s.w * s.h, `${at} ${s.where} "${s.name}": renders at ${s.w}×${s.h}`).toBeGreaterThan(0)
|
||
expect(s.shapes, `${at} ${s.where} "${s.name}": an <svg> with no shapes`).toBeGreaterThan(0)
|
||
if (!s.chart) {
|
||
expect(s.w, `${at} ${s.where} "${s.name}": icon width ${s.w}`).toBeGreaterThanOrEqual(12)
|
||
expect(s.w, `${at} ${s.where} "${s.name}": icon width ${s.w}`).toBeLessThanOrEqual(24)
|
||
}
|
||
}
|
||
}
|
||
|
||
for (const scheme of ['light', 'dark'] as const) {
|
||
for (const width of [375, 1440]) {
|
||
test(`/benchmarks draws every icon, in one set, under a ${scheme} scheme at ${width}px`, async ({ page }) => {
|
||
await page.emulateMedia({ colorScheme: scheme, reducedMotion: 'reduce' })
|
||
await page.setViewportSize({ width, height: 900 })
|
||
await page.route(api, (r) => r.fulfill({ json: listing(r, snapshot.data) }))
|
||
const hydrated = page.waitForRequest((r) => api(new URL(r.url())))
|
||
await page.goto(`${site.url}/benchmarks`)
|
||
await hydrated
|
||
check(await svgs(page), `${scheme} ${width}px index`)
|
||
const text = await page.locator('main').last().innerText()
|
||
expect(text.match(ARROWS)?.[0] ?? null, `${scheme} ${width}px: an arrow typed as a character`).toBeNull()
|
||
|
||
await expect(async () => {
|
||
await page.keyboard.press('/')
|
||
await expect(page.getByRole('dialog', { name: 'Jump to a benchmark' })).toBeVisible({ timeout: 500 })
|
||
}).toPass()
|
||
check((await svgs(page)).filter((s) => s.where === 'palette'), `${scheme} ${width}px palette`)
|
||
})
|
||
|
||
test(`a benchmark's own page draws every icon under a ${scheme} scheme at ${width}px`, async ({ page }) => {
|
||
await page.emulateMedia({ colorScheme: scheme, reducedMotion: 'reduce' })
|
||
await page.setViewportSize({ width, height: 900 })
|
||
await page.goto(`${site.url}/benchmarks/kai-banking77`)
|
||
check(await svgs(page), `${scheme} ${width}px detail`)
|
||
expect((await page.locator('main').last().innerText()).match(ARROWS)?.[0] ?? null).toBeNull()
|
||
})
|
||
}
|
||
}
|