Add typecheck + CI checks, light theme with self-hosted Inter, fix real-model test

- Typecheck: typescript 7 devDependency, `bun run typecheck`; CI gains a
  `check` stage (install, typecheck, fast tests) gating image builds (D-038).
- Light theme: every colour is a token; prefers-color-scheme: light
  overrides them. Inter Variable self-hosted from @fontsource-variable/inter,
  kept out of Bun's CSS inlining and precached (scripts/fonts.ts, D-037).
  Backend select no longer overflows on narrow phones.
- tests/live-real-model.test.ts: time budget scales with clip length and
  en_alien gets a measured WER bar; it had failed since July (D-039).
- Delete the temporary isolate_wer2.ts; fix icon "not checked in" comments
  and the README's model-weights location.
- Agent docs: branch naming, typecheck, CI, Bun CSS gotcha, permissions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-29 20:52:13 +01:00
1 parent 0ece4b3a2a
commit 4942461f79
21 files changed
+477 -113

No files matched your search

+17 -4
View File
@@ -15,6 +15,7 @@
import { cp, readdir, rm } from "node:fs/promises";
import { join, relative } from "node:path";
import { injectPrecacheManifest, precachePathsFromOutputs } from "../src/pwa/precache.ts";
import { ensureFonts, FONTS_DIR } from "./fonts.ts";
import { ensureIcons, ICON_SIZES, ICONS_DIR } from "./generate-icons.ts";
const ROOT = join(import.meta.dir, "..");
@@ -23,8 +24,11 @@ const DIST = join(ROOT, "dist");
await rm(DIST, { recursive: true, force: true });
// Icons before the shell build — index.html references src/pwa/icons/ (the
// HTML loader resolves the file at bundle time) and they aren't checked in.
// HTML loader resolves the file at bundle time). They are checked in, but
// regenerate any that are missing.
await ensureIcons(ICONS_DIR);
// Self-hosted Inter must exist for index.css to resolve (scripts/fonts.ts).
const fontFiles = await ensureFonts();
// 1. App shell (hashed assets).
const shellBuild = await Bun.build({
@@ -35,6 +39,8 @@ const shellBuild = await Bun.build({
sourcemap: "linked",
define: { "process.env.NODE_ENV": '"production"' },
env: "BUN_PUBLIC_*",
// Self-hosted fonts stay as plain url(fonts/...) files — see scripts/fonts.ts.
external: ["*.woff2"],
});
// 2. Inference worker at its stable URL (Bun does not follow `new Worker`
@@ -63,10 +69,13 @@ const ortFiles = (await readdir(ortDist)).filter((name) =>
for (const name of ortFiles) {
await cp(join(ortDist, name), join(DIST, "ort", name));
}
for (const name of fontFiles) {
await cp(join(FONTS_DIR, name), join(DIST, "fonts", name));
}
// 4. Service worker with the injected precache manifest (D-007). Icons,
// manifest, and the worker are unhashed, so the cache version hashes their
// contents (the hashed shell assets are covered by their names).
// manifest, the worker, and fonts are unhashed, so the cache version hashes
// their contents (the hashed shell assets are covered by their names).
const swBuild = await Bun.build({
entrypoints: [join(ROOT, "src/pwa/sw.ts")],
target: "browser",
@@ -82,10 +91,14 @@ const precachePaths = precachePathsFromOutputs(
"inference.worker.js",
"manifest.webmanifest",
...ICON_SIZES.map((size) => `icons/icon-${size}.png`),
...fontFiles.map((name) => `fonts/${name}`),
],
);
const fontBytes = await Promise.all(
fontFiles.map((name) => Bun.file(join(FONTS_DIR, name)).bytes()),
);
const version = Bun.hash(
JSON.stringify(precachePaths) + swSource + workerScript,
JSON.stringify(precachePaths) + swSource + workerScript + fontBytes.map((b) => Bun.hash(b)).join(),
).toString(36);
await Bun.write(join(DIST, "sw.js"), injectPrecacheManifest(swSource, precachePaths, version));
+43
View File
@@ -0,0 +1,43 @@
/**
* Self-hosted Inter (variable, upright) from @fontsource-variable/inter.
*
* src/index.css declares one @font-face per unicode-range subset with a
* relative `url(fonts/<file>)`, which resolves to src/fonts/ — a generated,
* gitignored copy of the package's files (`ensureFonts()`; Bun's CSS bundler
* must be able to resolve every url()).
*
* - Dev (`bun dev`): the HTML-import bundler inlines them as base64. Fine
* locally.
* - Production (scripts/build.ts): `external: ["*.woff2"]` keeps them out of
* the bundle — Bun inlines every resolvable CSS url() asset, which would put
* ~220 KB into the render-blocking stylesheet and defeat unicode-range lazy
* loading. The build copies them to dist/fonts/ and precaches them instead.
*
* Same-origin either way, so COEP `require-corp` (D-023) allows them.
*/
import { copyFile, mkdir } from "node:fs/promises";
import { join } from "node:path";
const PACKAGE_FILES = join(import.meta.dir, "../node_modules/@fontsource-variable/inter/files");
const CSS_PATH = join(import.meta.dir, "../src/index.css");
export const FONTS_DIR = join(import.meta.dir, "../src/fonts");
/** The font files a stylesheet references via `url(fonts/<file>)`. */
export function fontFilesInCss(css: string): string[] {
const files = [...css.matchAll(/url\(["']?fonts\/([^"')]+)["']?\)/g)].map((match) => match[1]!);
return [...new Set(files)];
}
/**
* Copy every font src/index.css references into src/fonts/ (idempotent).
* Always overwrites (~220 KB), so a package upgrade can't leave stale copies.
*/
export async function ensureFonts(): Promise<string[]> {
const files = fontFilesInCss(await Bun.file(CSS_PATH).text());
await mkdir(FONTS_DIR, { recursive: true });
for (const name of files) {
await copyFile(join(PACKAGE_FILES, name), join(FONTS_DIR, name));
}
return files;
}