The web user-interface for the BTClock
  • TypeScript 61.6%
  • Svelte 33.7%
  • Python 2.7%
  • CSS 1.6%
  • JavaScript 0.2%
  • Other 0.1%
Find a file
Djuri Baars e2e8a3da3b
All checks were successful
/ check-changes (push) Successful in 19s
/ build (push) Successful in 11m34s
feat!: migrate to SvelteKit 3 and adapter-static 4
SvelteKit 3.1.0 clears the last three `pnpm audit` findings, which have
no 2.x fix: GHSA-pfhg-q3qv-9q6r and GHSA-27mw-82g8-phjj in kit itself,
and GHSA-pxg6-pf52-xh8x in the cookie 0.6 it bundled (kit 3 uses
cookie 2). `pnpm audit` now reports no known vulnerabilities.

The mechanical part ran through `sv migrate sveltekit-3`: `$lib` imports
become `#lib/*.js` subpath imports declared in package.json,
`$app/environment` becomes `$app/env`, and tsconfig.json extends
`$app/tsconfig`. On top of that:

- svelte.config.js is gone. Its options live in kit.config.ts, which
  both vite.config.ts and vite.config.test.ts pass to sveltekit(); the
  migration tool only rewired vite.config.ts, so the Playwright build
  would otherwise have run without the adapter, appDir and
  bundleStrategy: 'single'.
- The LittleFS filename patch is rebased onto kit 3.1.0. Kit 3 moved
  the client entry/chunk/asset naming around; the output names are the
  same as before (build/immutable/<hash>.js, assets/<hash>.<ext>).
- Kit 3 runs the adapter in a buildApp post hook, after closeBundle, so
  the bundle.html cleanup moved to buildApp (post, after sveltekit()).
  Without that the SPA fallback ended up in the LittleFS image again.
- version.pollInterval is set to 0. Kit 3 polls build/version.json
  hourly by default; nothing reads updated.current and every poll hits
  the device. Kit 3 also checks on window focus/visibility, which has
  no switch; that is one small GET per focus.
- PUBLIC_BASE_URL moves from the deprecated $env/static/public to a
  static public variable in src/env.ts, read via $app/env/public. The
  schema defaults it to '', which replaces the Object.hasOwn guard.
  An empty PUBLIC_BASE_URL still overrides .env (checked against a
  .env pointing at a device), so the Playwright and CI builds keep
  same-origin requests. Vitest gets a $app/env/public mock.
- Source-regex specs that pin import lines expect the new `.js`
  specifiers.

TypeScript stays on 6: kit 3 (^6.0.0), svelte-check and typescript-eslint
do not accept 7 yet.

Gzipped LittleFS payload: 322262 bytes (was 319200; limit 410000).
2026-10-25 21:33:55 +01:00
.forgejo/workflows ci: keep pnpm/action-setup at v5 (v6 self-installer breaks on runner Node 18) 2026-06-26 21:36:25 +02:00
.github test(e2e): run Playwright integration tests on Firefox too 2026-06-09 20:16:44 +02:00
.vscode Added WiFi signal status and settings 2023-11-21 16:05:00 +01:00
doc feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
extra/icons Remove icons to save space in image 2024-09-03 12:37:35 +02:00
patches feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
project.inlang feat(i18n): add Italian, Polish, Turkish, Czech & Danish (15 locales) 2026-06-05 21:00:49 +02:00
scripts feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
src feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
static feat(settings): frontlight brightness ceiling 2048, reset telemetry in the spec 2026-10-25 19:54:30 +01:00
tests feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
.editorconfig chore: add .editorconfig 2026-04-30 03:18:20 +02:00
.gitignore chore(build): add opt-in bundle visualizer 2026-04-30 03:18:00 +02:00
.nvmrc feat(i18n): add French, Arabic, Portuguese & Russian with full RTL support 2026-06-05 20:32:44 +02:00
.prettierignore chore(prettier): ignore Paraglide-generated artifacts in project.inlang/ 2026-05-14 12:51:56 +02:00
.prettierrc feat(webui): promote webui-v2 to repo root 2026-04-17 02:06:52 +02:00
AGENTS.md docs: refresh AGENTS.md + README to match current codebase 2026-06-05 18:38:23 +02:00
Dockerfile chore(docker): bump base image to Node 24 LTS 2026-04-17 14:17:36 +02:00
eslint.config.js chore: ignore .pnpm-store for Prettier, ESLint, and git 2026-04-17 02:12:31 +02:00
gzip_build.py WebUI: priceSymMode, availableFonts hasBtcSymbol, Playwright API coverage 2026-05-09 19:39:21 +02:00
kit.config.ts feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
package.json feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
playwright.base.ts test(e2e): de-flake Playwright in CI (offline initMock, timeouts, retry) 2026-06-05 19:33:46 +02:00
playwright.config.ts test(e2e): run Playwright integration tests on Firefox too 2026-06-09 20:16:44 +02:00
playwright.doc-screenshot.config.ts docs(readme): refresh screenshots with live framebuffer + REV_B mocks 2026-05-14 12:52:08 +02:00
playwright.screenshot.config.ts chore(playwright): factor shared config into playwright.base.ts 2026-04-30 03:09:54 +02:00
pnpm-lock.yaml feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
pnpm-workspace.yaml feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
README.md feat(i18n): add Italian, Polish, Turkish, Czech & Danish (15 locales) 2026-06-05 21:00:49 +02:00
renovate.json Migrate config renovate.json 2024-12-26 14:29:21 +00:00
tsconfig.json feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
vite.config.test.ts feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00
vite.config.ts feat!: migrate to SvelteKit 3 and adapter-static 4 2026-10-25 21:33:55 +01:00

BTClock WebUI

Latest release BTClock CI

The web user-interface for the BTClock.

Screenshot Screenshot Dark

Getting started

This project is managed with pnpm. Install it with corepack enable pnpm or npm i -g pnpm if you don't have it yet.

pnpm install           # applies the SvelteKit filename-shortening patch via pnpm patchedDependencies
pnpm dev               # start the dev server (http://localhost:5173)

Set PUBLIC_BASE_URL in .env to the address of a real BTClock (e.g. http://btclock-d60b14.local) so the dev server talks to it. When unset it falls back to an empty string — i.e. same-origin requests — which is also what a build the firmware serves from LittleFS wants, so leave it empty at build time.

Production build

pnpm build             # produces dist/ with prerendered index.html + hashed JS/CSS
python3 gzip_build.py  # gzips everything under dist/ into build_gz/
mklittlefs -c build_gz -s 409600 output/littlefs.bin

The firmware serves files literally out of /lfs/www/ (see control_server.cpp), so the build keeps that directory minimal: only / is prerendered, and a tiny post-build step in vite.config.ts deletes adapter-static's bundle.html SPA fallback (the firmware doesn't route unknown paths to it). /api and /convert opt out of prerendering and are reached only via SvelteKit client routing. The filename-shortening patch in patches/ keeps asset names within LittleFS's filename limit.

Tests

pnpm test:unit              # vitest unit + component tests
pnpm test:integration       # playwright end-to-end suite (chromium + mobile)
pnpm test:screenshots       # device/locale screenshot suite
pnpm doc:update-screenshots

Languages

The WebUI ships 15 translations. Message catalogs live in src/lib/locales/<code>.json and are compiled by Paraglide. The language picker lists them sorted alphabetically by their localized name; Arabic is rendered right-to-left (<html dir="rtl">), with technical identifiers (IP/MAC/hostname, version and commit) kept left-to-right.

🇬🇧 English (en) · 🇳🇱 Nederlands (nl) · 🇩🇪 Deutsch (de) · 🇪🇸 Español (es) · 🇫🇷 Français (fr) · 🇮🇹 Italiano (it) · 🇵🇹 Português (pt) · 🇵🇱 Polski (pl) · 🇨🇿 Čeština (cs) · 🇩🇰 Dansk (da) · 🇹🇷 Türkçe (tr) · 🇷🇺 Русский (ru) · 🇸🇦 العربية (ar) · 🇨🇳 中文 (zh) · 🇯🇵 日本語 (ja)

To add a locale: copy src/lib/locales/en.json and translate the values, add the tag to project.inlang/settings.json, and register the locale + flag in src/lib/i18n.svelte.ts.

Key architectural choices

  • Feature-based folders — UI is organised under src/lib/features/{control,status,firmware,settings,convert} with colocated sub-components and spec tests. Generic primitives live in src/lib/ui/, data access in src/lib/api/, and global state in src/lib/stores/ as Svelte 5 runes.
  • Discriminated-union state — SettingsState and StatusState encode loading | error | ready, so every consumer handles each phase explicitly instead of falling through nullable props.
  • Valibot schemas — Runtime validation for every inbound API payload lives in src/lib/api/schemas.ts, protecting the UI from firmware drift.
  • Paraglide JS v2 — Message catalogs in src/lib/locales/ are compiled per-locale and tree-shaken by the Paraglide Vite plugin. See Languages for the 15 shipped locales.
  • Static output only — @sveltejs/adapter-static with SSR disabled; the WebUI is always mounted on-device by the firmware.
  • Minimal font footprint — only the latin-400 woff2 file for Ubuntu (plus compact sats-symbol subsets) is shipped, keeping build_gz/ well below the ~420 KB LittleFS partition.