The web user-interface for the BTClock
  • TypeScript 61.9%
  • Svelte 33.5%
  • Python 2.6%
  • CSS 1.6%
  • JavaScript 0.2%
  • Other 0.1%
Find a file
Djuri Baars d7520f6459
All checks were successful
/ check-changes (push) Successful in 4s
/ build (push) Successful in 11m46s
fix(settings): show the corsDisabled switch only when the firmware has it
50eb41d bound the switch unconditionally. On firmware (and test
fixtures) without corsDisabled, bind:checked seeded `false` into the
settings store on mount, so the form was dirty before any input: the
dirty badge showed and the Playwright capability-gate specs for debt
screens and the distributed display failed on that badge in CI.

Gate it on `'corsDisabled' in data`, like digitFontPx and the dist
fields. New spec: without the key the switch is hidden, the form stays
pristine and a PATCH carries no corsDisabled; with it the switch shows
the device value and toggling PATCHes it.
2026-11-02 11:58:20 +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 fix(settings): show the corsDisabled switch only when the firmware has it 2026-11-02 11:58:20 +01:00
static docs(openapi): show/custom takes screenChange and fullRefresh 2026-11-02 11:52:52 +01:00
tests fix(settings): show the corsDisabled switch only when the firmware has it 2026-11-02 11:58:20 +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(settings): corsDisabled switch; tolerate blanked password fields 2026-11-02 02:18:20 +01: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.

The device rejects API calls from other origins unless corsDisabled is on, so enable it first (Settings → System on the device's own WebUI, or curl -X PATCH -H 'Content-Type: application/json' -d '{"corsDisabled":true}' http://<device>/api/settings). Switch it off again when you're done.

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.