Files
orca/config/scripts/component-fixtures/fixture-paths.mjs
T
Neil a30d4f4f28 feat(devex): component fixtures with headless screenshots for fast UI checks (#33406)
* feat(devex): component fixtures with headless screenshots for fast UI checks

Colocated *.fixture.tsx files export named fixtures (render + optional store
seed, setup, interact, width). A dev-only Vite entry (vite.fixtures.config.ts,
extending vite.web.config.ts) renders one fixture in isolation with the real
I18nProvider, TooltipProvider, theme class, default-seeded store, and the web
client's preload bridge (installWebPreloadApi) as window.api.

`pnpm fixtures:shot <id|glob> [--theme dark|light|both]` starts or reuses a
detached server (Vite + one headless Chromium) and writes PNGs to
.cache/fixtures/. A warm page re-renders in place (~100ms/shot) and reloads
only after source changes. A stdio MCP server (registered in .mcp.json)
exposes list_fixtures and screenshot_fixture.

Fixtures load only through import.meta.glob in the fixture entry, which is
not a production build input; oxlint rejects static imports of *.fixture.

* Keep fixture config and agent config inside reviewed root entries

* Baseline the fixture page's default-settings seed in the focus-read ratchet

* docs(agents): shorten fixtures pointer

* Allowlist the dev-only fixture explorer's labels in localization coverage
2026-10-25 00:23:32 -07:00

42 lines
1.6 KiB
JavaScript

import { readdirSync, statSync } from 'node:fs'
import path from 'node:path'
export const repoRoot = path.resolve(import.meta.dirname, '../../..')
export const fixtureCacheDir = path.join(repoRoot, '.cache', 'fixtures')
export const serverStatePath = path.join(fixtureCacheDir, 'server.json')
export const serverLockPath = path.join(fixtureCacheDir, 'server.lock')
export const serverLogPath = path.join(fixtureCacheDir, 'server.log')
export const serverScriptPath = path.join(
repoRoot,
'config',
'scripts',
'component-fixtures',
'fixture-server.mjs'
)
// Why: a detached server outlives edits to these scripts; clients restart it on mismatch.
export function fixtureServerCodeStamp() {
const scriptDir = path.dirname(serverScriptPath)
const configPath = path.join(repoRoot, 'config', 'vite.fixtures.config.ts')
const files = [...readdirSync(scriptDir).map((name) => path.join(scriptDir, name)), configPath]
return String(Math.max(...files.map((file) => statSync(file).mtimeMs)))
}
export function fixtureScreenshotPath(outDir, id, theme) {
const fileStem = id
.replaceAll('/', '__')
.replaceAll(':', '--')
.replace(/[^\w.-]/g, '_')
return path.join(outDir, `${fileStem}.${theme}.png`)
}
// `*` matches within the id; a bare component id (no `:`) matches all of its fixtures.
export function matchFixtureIds(knownIds, pattern) {
if (!pattern.includes('*')) {
return knownIds.filter((id) => id === pattern || id.startsWith(`${pattern}:`))
}
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replaceAll('*', '.*')
const matcher = new RegExp(`^${escaped}$`)
return knownIds.filter((id) => matcher.test(id))
}