7b. Offline-first frontend assets
BigFred runs on club LANs and on the hub OS image without any Internet
access — neither the operator's browser nor the Go server may depend
on third-party CDNs, Google Fonts, or runtime npm/wget fetches.
7b.1 Requirements
| Layer | Rule |
|---|---|
| Browser | After the SPA loads, every font, stylesheet, script, worker, image and audio file is served from the same origin as the API (embedded web/dist or --static-dir). No <link href="https://…">, no dynamic import() of external modules, no fetch() to off-origin URLs for UI assets. |
| Build | Vite resolves all JS/CSS dependencies from node_modules at npm run build time. Fonts ship via @fontsource/* (or equivalent self-hosted files under web/public/). Sounds and icons are either imported in TS (import url from "…") or live under web/public/ and are copied into dist/ by Vite. |
| Server | loco-server and bigfred-os-ui never download UI assets at startup. Production binaries embed a pre-built web/dist via go:embed. |
| Development | Vite may proxy /api to localhost; that is dev-only. Production acceptance is judged on the dist/ artefact, not on :5173. |
Club rooms and the Raspberry Pi hub are treated as air-gapped for UI purposes: if the bundle builds and passes the offline check, the runtime works on an isolated VLAN.
7b.2 What is bundled today
| Asset class | Mechanism | Location |
|---|---|---|
| React, MUI, TanStack Query, i18n | npm dependencies → Vite chunks |
web/dist/assets/*.js |
| Roboto (UI) | @fontsource/roboto imported in main.tsx |
hashed .woff2 beside JS/CSS |
| JetBrains Mono (hub logs) | @fontsource/jetbrains-mono in hub OS UI |
same |
| MUI icons | tree-shaken from @mui/icons-material |
JS bundle |
| Throttle / function SVGs | import … from "*.svg" |
inlined or hashed files |
| Radio / radiostop / PA Ogg | import … from "*.ogg" or web/public/sounds/ |
dist/assets/ or dist/sounds/ |
| Locale JSON | static import in web/src/i18n/index.ts |
JS chunks (§7c.10) |
Forbidden in index.html and application source: fonts.googleapis.com,
fonts.gstatic.com, unpkg.com, jsdelivr.net, cdnjs.cloudflare.com, and
any other runtime CDN.
7b.3 Build pipeline
Both frontends share the same pattern:
cd web && npm ci && npm run build
# vite build → dist/
# check-offline-bundle.mjs scans dist/ for http(s):// (excluding .map)
npm run build runs the offline check automatically. CI and make web-build
must not skip it. The checker flags forbidden CDN hostnames, external
<link> / <script> tags in index.html, CSS @import url(https://…), and
dynamic import("https://…") — it intentionally ignores SVG xmlns URIs and
React error-decoder strings that are never fetched at runtime.
Hub OS UI (bigfred-os/apps/bigfred-os-ui):
make -C apps/bigfred-os-ui build # web-build + go:embed all:web/dist
Main BigFred SPA (bigfred/web):
make web-build # produces web/dist for go:embed (§7.1)
7b.4 Future dependencies (Monaco, maps, …)
When a feature needs a large third-party runtime (Monaco editor, map tiles, …):
- Add it as an
npmdependency, not a<script src="https://…">. - Configure Vite to copy workers/WASM into
dist/(e.g.vite-plugin-monaco-editoror@monaco-editor/reactwithMonacoEnvironment.getWorkerUrlpointing at a local/assets/…path). - Extend
scripts/check-offline-bundle.mjsonly if new file types need coverage; the default scan already catches leaked URLs in.html,.js,.cssand.json.
Map tiles are out of scope for v1 (no basemap). If they are added later,
tile packs must be pre-seeded on the hub image or served from /tiles/ on
the same host — never from a public tile CDN at runtime.
7b.5 Acceptance criteria
npm run buildexits 0 and printsoffline bundle OK.- With the server host and client on a network without a default route
to the Internet, loading
/renders login, typography and icons correctly (no pending requests to non-LAN hosts in DevTools → Network). check-offline-bundle.mjspasses ondist/(no forbidden CDN hosts, no external<link>/<script>in HTML, no external CSS imports).- Adding a CDN link to
index.htmlwithout updating the policy must fail CI viacheck-offline-bundle.mjs.