test-web / test (push) Successful in 42s
app.html linked its stylesheet straight from fonts.googleapis.com, with preconnects to that host and fonts.gstatic.com. A deployed instance has no outbound network, so those requests never arrive and the whole UI renders in fallback faces — Georgia for the display face, whatever the system has for Inter and JetBrains Mono. This is invisible in development, which is why it survived: the dev machine has internet, so the fonts load and everything looks right. Only a real deployment shows the failure. tools/vendor-fonts.py fetches the three families once and writes them under web/static/fonts with a generated stylesheet. static/ is copied into the SvelteKit build, which Go embeds, so the faces travel inside the binary. 32 woff2 files, 912K. Two details that matter for correctness rather than size: Urls in the generated CSS are relative (./Inter-400-latin.woff2), not absolute. A url() resolves against the stylesheet's own address, so the directory keeps working when the app is served under a base path; /fonts/... would not. Every subset Google slices is kept, with unicode-range intact. The browser still fetches only the ranges a page uses, so this costs repository bytes rather than request bytes — and a library full of Cyrillic or Greek artist names renders instead of falling back mid-list. The guard asserts the property, not the vendor: any absolute url in a resource-loading attribute fails, whoever hosts it, since naming Google would pass the day someone reached for a different CDN. It also checks preconnect separately (those carry no fetch of their own, so the url check misses them), strips HTML comments before asserting an absence so prose describing the forbidden thing cannot satisfy the check, and pins the font families to tokens.json rather than a hardcoded list. Falsified against the pre-change app.html: it trips both the external-url and preconnect assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
46 lines
2.0 KiB
HTML
46 lines
2.0 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<!--
|
|
SVG first: it flips the hat with the viewer's colour scheme, which the PNG
|
|
can't. The PNG is the fallback for browsers without SVG-favicon support
|
|
and is plated for the same reason apple-touch-icon is — see brand/.
|
|
Ordering matters: browsers take the last icon they understand, so the
|
|
PNG must come FIRST or it wins over the SVG in Chrome.
|
|
-->
|
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" sizes="32x32" />
|
|
<link rel="icon" href="%sveltekit.assets%/brand/favicon.svg" type="image/svg+xml" />
|
|
<link rel="apple-touch-icon" href="%sveltekit.assets%/apple-touch-icon.png" />
|
|
<!-- Obsidian: matches --fs-surface-page so mobile browser chrome doesn't
|
|
seam against the app's own background. -->
|
|
<meta name="theme-color" content="#14171A" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Minstrel</title>
|
|
<script>
|
|
(function () {
|
|
try {
|
|
var t = localStorage.getItem('minstrel:theme') || 'system';
|
|
var resolved = t === 'system'
|
|
? (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')
|
|
: t;
|
|
document.documentElement.setAttribute('data-theme', resolved);
|
|
} catch (e) {
|
|
document.documentElement.setAttribute('data-theme', 'dark');
|
|
}
|
|
})();
|
|
</script>
|
|
<!-- Fonts are vendored into web/static/fonts by tools/vendor-fonts.py and
|
|
served from our own origin. A deployed instance has no outbound
|
|
network, so a provider stylesheet would simply never arrive and the
|
|
UI would render in fallback faces — hence no font-provider link and
|
|
no preconnect to a host we don't run. -->
|
|
<link rel="stylesheet" href="%sveltekit.assets%/fonts/fonts.css" />
|
|
%sveltekit.head%
|
|
<!-- MINSTREL_TOKENS -->
|
|
</head>
|
|
<body class="bg-background text-text-primary">
|
|
<div style="display: contents">%sveltekit.body%</div>
|
|
</body>
|
|
</html>
|