CLAUDE.md, AGENTS.md, ARCHITECTURE.md and TODO.md all said no browser exists
in this environment, so appearance had never been verified by anyone. Chrome
is on PATH. Its own --screenshot flag is what fails: it fires on the load
event, before this app's async IndexedDB read resolves, and captures a blank
page — which is presumably how the claim took hold.
scripts/screenshot.ts drives CDP instead: waits past the load event, seeds
IndexedDB via --eval-file and reboots the app with --reload, clicks with
--click, applies shadcn's class-based dark palette with --dark, and reports
console errors and uncaught exceptions. A page that renders blank from a
thrown error looks identical to an empty one, so the console output is half
the value.
Two defects found by simply looking:
- The month table's numeric columns had no horizontal padding, so figures ran
into each other: "+7,175.00 DKK4,200.00 DKK". Now padded.
- Dev logged "The script has an unsupported MIME type ('text/html')" on every
boot, because registerServiceWorker asks for sw.js and the "/*" catch-all
answered with the HTML shell. Dev now serves an inert worker.
Confirmed working, first time seen: the carry meter (including red where a post
is overspent), the Task 5 rule-history editor, the Task 6 "change from here"
control, and baked FX rates showing as "manual - 2026-09-01" in Settings.
Recorded in the docs, with the limits stated: this container's fontconfig maps
every generic family to a monospace face, so screenshots need a FONTCONFIG_FILE
override or you will chase a font bug that does not exist; Chrome starts from
an empty profile each run; and no screenshot can judge how a design feels.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
51 lines
2.1 KiB
TypeScript
51 lines
2.1 KiB
TypeScript
import { serve } from "bun";
|
|
import index from "./index.html";
|
|
import { normalizeBase } from "./ui/basePath.ts";
|
|
import { buildManifest } from "./manifest.ts";
|
|
|
|
const basePath = normalizeBase(process.env.BUN_PUBLIC_BASE_PATH);
|
|
|
|
const server = serve({
|
|
routes: {
|
|
// index.html references both of these with a plain <link href>. Without
|
|
// explicit routes the "/*" catch-all below answers them with the HTML
|
|
// shell, so the manifest arrives as text/html (a parse error, and no
|
|
// install prompt) and the icon never loads. Generated rather than served
|
|
// from a committed file so the dev manifest cannot drift from the built
|
|
// one — a stale copy is exactly how it came to hardcode scope "/".
|
|
[`${basePath}manifest.webmanifest`]: () =>
|
|
new Response(buildManifest(basePath), {
|
|
headers: { "content-type": "application/manifest+json" },
|
|
}),
|
|
[`${basePath}icon.svg`]: () =>
|
|
new Response(Bun.file(new URL("./icon.svg", import.meta.url)), {
|
|
headers: { "content-type": "image/svg+xml" },
|
|
}),
|
|
[`${basePath}icon-192.png`]: () =>
|
|
new Response(Bun.file(new URL("./icon-192.png", import.meta.url)), {
|
|
headers: { "content-type": "image/png" },
|
|
}),
|
|
[`${basePath}icon-512.png`]: () =>
|
|
new Response(Bun.file(new URL("./icon-512.png", import.meta.url)), {
|
|
headers: { "content-type": "image/png" },
|
|
}),
|
|
// registerServiceWorker() asks for sw.js on every boot. Without this the
|
|
// catch-all answers with the HTML shell and the browser logs
|
|
// "The script has an unsupported MIME type ('text/html')" — caught and
|
|
// harmless, but noise that hides real errors. A worker that claims no
|
|
// clients and handles no fetches keeps registration honest in dev without
|
|
// caching anything, which would fight HMR.
|
|
[`${basePath}sw.js`]: () =>
|
|
new Response("/* dev: intentionally does nothing */\n", {
|
|
headers: { "content-type": "text/javascript", "cache-control": "no-cache" },
|
|
}),
|
|
"/*": index,
|
|
},
|
|
development: process.env.NODE_ENV !== "production" && {
|
|
hmr: true,
|
|
console: true,
|
|
},
|
|
});
|
|
|
|
console.log(`Dev server running at ${server.url}`);
|