From 0e6ebe562e4b69bc15a332ee9c5616a70ceef60d Mon Sep 17 00:00:00 2001 From: Chris Thompson Date: Sun, 13 Sep 2026 19:54:43 -0600 Subject: [PATCH] webui: make the committed bundle reproducible dist/index.html is committed and embedded into the server binary at configure time (CMakeLists.txt:2226), which is what lets the project build a working server without a JavaScript toolchain. But the bundle is not reproducible: SvelteKit defaults kit.version.name to Date.now() and derives the __sveltekit_ global it embeds from it, so two builds of identical source differ. before: build 1 __sveltekit_1ia7gsf sha256 bb3f9c3c... build 2 __sveltekit_1xqgjp8 sha256 6cd91a94... after: build 1 __sveltekit_1vzi1g sha256 65bed68b... build 2 __sveltekit_1vzi1g sha256 65bed68b... Any two branches that rebuild the web UI therefore conflict in that file whether or not their source changes overlap -- which is what happened to #539, where dist/index.html was the only conflict while src/lib/text.ts merged cleanly and no upstream commit had touched either file. That the id is a pure function of this setting was confirmed by building with the timestamp the committed bundle carries: the derived id came back as __sveltekit_ega6lw, and the result was byte-identical to the committed file. The package version is used rather than a constant. It is stable for a given source tree, so the bundle reproduces, and it changes when the version is bumped, so SvelteKit's client-side "app has been updated" check keeps working across releases. A hard-coded string would have disabled that silently. dist/index.html is rebuilt here so the tree is consistent: after this, running npm run build leaves git clean instead of producing a diff every time. Normalising the build id, the version constant and the Vite content hashes -- all three derived from this one setting -- leaves zero differing lines against the committed bundle, so nothing else in the UI changes. Reported as #545. --- webui/native/dist/index.html | 8 ++++---- webui/native/svelte.config.js | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/webui/native/dist/index.html b/webui/native/dist/index.html index 6902014dd..0e958324f 100644 --- a/webui/native/dist/index.html +++ b/webui/native/dist/index.html @@ -31,15 +31,15 @@
diff --git a/webui/native/svelte.config.js b/webui/native/svelte.config.js index a5ce70d05..8a17ed9cd 100644 --- a/webui/native/svelte.config.js +++ b/webui/native/svelte.config.js @@ -1,5 +1,21 @@ +import { readFileSync } from 'node:fs'; + import adapter from '@sveltejs/adapter-static'; +// SvelteKit defaults kit.version.name to Date.now(), and derives the +// __sveltekit_ global it embeds in dist/index.html from it. That makes the +// built bundle different on every run, and dist/index.html is committed -- so +// any two branches that rebuild the web UI conflict in it, whether or not their +// source changes overlap. +// +// The package version is stable for a given source tree, so the bundle is +// reproducible, while still changing when the version is bumped -- which is +// what SvelteKit's client-side "app has been updated" check needs to keep +// working across releases. +const { version } = JSON.parse( + readFileSync(new URL('./package.json', import.meta.url), 'utf8') +); + /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { @@ -17,6 +33,9 @@ const config = { }, paths: { relative: true + }, + version: { + name: version } } };