1
0
Fork 0
forked from btclock/webui
The web user-interface for the BTClock
  • TypeScript 61.3%
  • Svelte 33.4%
  • Python 2.9%
  • CSS 1.7%
  • JavaScript 0.5%
  • Other 0.1%
Find a file
Djuri Baars 244ec500ac
fix(upload): POST firmware/WebUI as raw octet-stream, not multipart
xhrUpload wrapped the file in a FormData, so the browser sent
multipart/form-data. The device's /upload/{firmware,webui} handlers
stream the raw request body straight to flash and do NOT parse
multipart, which broke uploads two ways:

  - Content-Length is inflated by the multipart envelope. Storage
    images are sized to the exact LittleFS partition, so the ~250 B of
    boundary + Content-Disposition overhead trips the firmware's 413
    size gate ({"error":"oversize","max":839680} on Rev B).
  - Even under the gate, the `--boundary` preamble would be written
    into flash as the start of the image, corrupting the LittleFS
    superblock / failing the esp_ota app-image magic check.

Send the file as a raw octet-stream (xhr.send(file) + explicit
Content-Type) so Content-Length == the image size and the bytes land
verbatim. Verified end-to-end on a Rev B device: both WebUI
(839680 B) and firmware (1758960 B, sha256-checked) uploads succeed.

Add a unit test asserting the raw-octet-stream request shape and a
representative Playwright test that drives the real file-input ->
button flow and asserts the captured request is byte-exact (not a
multipart wrapper); the Playwright test fails against the old code.
2026-06-06 01:37:54 +02:00
.forgejo/workflows ci(forgejo): bump forgejo-release action to v2.13.0 2026-06-05 18:42:04 +02:00
.github ci: install only Playwright chromium + fix cache-hit gating 2026-05-23 01:49:15 +02:00
.vscode Added WiFi signal status and settings 2023-11-21 16:05:00 +01:00
doc docs(readme): refresh screenshots with live framebuffer + REV_B mocks 2026-05-14 12:52:08 +02:00
extra/icons Remove icons to save space in image 2024-09-03 12:37:35 +02:00
patches chore(deps): update dependencies and migrate SvelteKit patch to pnpm 2026-06-05 17:52:54 +02:00
project.inlang feat(i18n): add Italian, Polish, Turkish, Czech & Danish (15 locales) 2026-06-05 21:00:49 +02:00
scripts WebUI: priceSymMode, availableFonts hasBtcSymbol, Playwright API coverage 2026-05-09 19:39:21 +02:00
src fix(upload): POST firmware/WebUI as raw octet-stream, not multipart 2026-06-06 01:37:54 +02:00
static docs(openapi): realign spec with firmware control_server routes 2026-06-05 18:30:01 +02:00
tests fix(upload): POST firmware/WebUI as raw octet-stream, not multipart 2026-06-06 01:37:54 +02: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
package.json chore: modernization cleanup (dead mocks, scratch file, pnpm, node) 2026-06-05 19:13:06 +02: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 chore(playwright): factor shared config into playwright.base.ts 2026-04-30 03:09:54 +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 chore: modernization cleanup (dead mocks, scratch file, pnpm, node) 2026-06-05 19:13:06 +02:00
pnpm-workspace.yaml chore(deps): update dependencies and migrate SvelteKit patch to pnpm 2026-06-05 17:52:54 +02: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
svelte.config.js docs(convert): document the firmware-imposed bundle constraint 2026-04-26 18:54:17 +02:00
tsconfig.json chore(tsconfig): turn on stricter checks 2026-04-30 03:08:16 +02:00
vite.config.test.ts build: drop dead vite config + deprecated esbuild minifier 2026-06-05 19:05:36 +02:00
vite.config.ts build: drop dead vite config + deprecated esbuild minifier 2026-06-05 19:05:36 +02: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 stateSettingsState 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.