Skip to content

webui: make the committed bundle reproducible - #546

Draft
christopherthompson81 wants to merge 1 commit into
0xShug0:mainfrom
christopherthompson81:webui/deterministic-bundle
Draft

christopherthompson81 wants to merge 1 commit into
0xShug0:mainfrom
christopherthompson81:webui/deterministic-bundle

Conversation

@christopherthompson81

Copy link
Copy Markdown
Contributor

Fixes #545.

dist/index.html is committed and embedded into the server binary at configure time (CMakeLists.txt:2226) — that is what lets the project build a working server without a JavaScript toolchain. But it is not reproducible: SvelteKit defaults kit.version.name to Date.now() and derives the embedded __sveltekit_<id> from it.

build 1 build 2
before __sveltekit_1ia7gsf / bb3f9c3c… __sveltekit_1xqgjp8 / 6cd91a94…
after __sveltekit_1vzi1g / 65bed68b… __sveltekit_1vzi1g / 65bed68b…

So any two branches that rebuild the web UI conflict in that file regardless of whether their source overlaps. That is what happened to #539, where dist/index.html was the only conflict while src/lib/text.ts merged cleanly and no commit on main had touched either file.

Uses the package version rather than a constant: stable for a given source tree, so the bundle reproduces, but 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.

Verification

The embedded id is a pure function of this setting. Building current main with the timestamp its committed bundle carries (1789350153403) produced __sveltekit_ega6lw — matching it — and a file byte-identical to the committed one. So this toolchain reproduces what is in the repo, and the setting is the only thing steering the id.

Nothing else in the UI changes. dist/index.html is rebuilt here so the tree is consistent — after this, npm run build leaves git status clean instead of producing a diff on every run. The rebuilt bundle differs from the committed one only in the version constant and the two things derived from it (the SvelteKit id, and the Vite content hashes, since the version string lives inside a chunk). Normalising those three leaves zero differing lines.

Alternatives considered

Stop committing dist/. This was the first idea and it has a real cost: the CMake step above falls back to a placeholder page when the file is absent, and the fallback succeeds. Removing the bundle would mean either requiring node for every build, or quietly shipping a server whose UI is a placeholder for anyone who did not run npm run build first. Making the artifact deterministic fixes the conflicts without giving that up.

.gitattributes with -merge on the file would stop git attempting a line-merge of minified output. Conflicts would still occur, but resolve as "take either, then rebuild" rather than producing a corrupted hybrid. Worth doing as well, perhaps, but it treats the symptom.

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_<id> 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 0xShug0#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 0xShug0#545.
@christopherthompson81

Copy link
Copy Markdown
Contributor Author

Moving this to draft so it isn't sitting in your review queue while you think over the trade-offs in #545 — no pressure either way, just ping me and I'll mark it ready again.

@christopherthompson81
christopherthompson81 marked this pull request as draft September 15, 2026 00:06
@0xShug0

0xShug0 commented Sep 15, 2026

Copy link
Copy Markdown
Owner

@mirek190 Any thoughts on this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

webui/native/dist/index.html is committed but not reproducible, so any two web UI PRs conflict

2 participants