The web app now carries a build stamp: a closed disclosure at the foot of the pane, beside the diagnostics one and drawn with the same chrome, saying which copy of the app this tab is running.
A deployed SPA is otherwise anonymous — a set of hashed files with nothing in them that says where they came from — so a tab three deploys behind and a tab with a real bug look identical. Every existing surface reports on the space; nothing reported on the app.
What it shows #
Build — 0e1a2b3c4d · Aug 06, 14:22 closed. Open, it holds:
commit |
the whole sha, or not recorded — built outside a git checkout |
working tree |
present only when the build came from a modified tree, since the commit then names code that is not this |
built |
the ISO instant the bundle was assembled |
version |
@radial/ui's own |
built for / served from |
the origin client-metadata.json declares, beside the one this tab is actually on |
browser transport |
whether the WASM iroh binding reached the bundle |
The last two change what the bundle can do rather than merely naming it:
- A deployed tab's client id is
<this origin>/client-metadata.json(auth.svelte.ts), so a bundle served anywhere but the origin it was built for carries a document that disagrees with the URL it was fetched from, and the authorization server refuses the client — silently, from the tab's point of view. The panel says so in a sentence, and says the same about a bundle built with no origin and served from a public one.isLoopbackHostmoved intoclient-metadata.tsand is now shared withauth.svelte.ts, so apreviewon 127.0.0.1 — which signs in through the loopback client id and never reads the document — is not warned about, and the warning and the client it is about cannot drift apart. copy-wasm.mjsis best-effort by design, and a bundle built without the binding reports having no endpoint in every private space — indistinguishable from a peer that never answered until something says the reason is the build.
Where it is #
+layout.svelte, in both shells: under the space's diagnostics in the pane, and under the space
picker, where a reader who cannot get past the front door is exactly the one being asked which build
they have. Unlike the diagnostics beside it, it is never absent — the moment somebody most needs
to know which build they are looking at is the moment the app appears to be fine. The pane foot is
now one .foot group that owns the rule and the gap, so a second disclosure continues the first
rather than starting another footer. DESIGN.md records it beside the diagnostics disclosure.
How the facts get in #
packages/ui/scripts/build-stamp.mjs asks git at build time; vite.config.ts injects the result as
__RADIAL_BUILD__; src/lib/build.ts is the pure half the panel reads (every input an argument, so
the one runtime value — where the tab is served from — is passed in).
It never throws and never fails a build. A tarball, a checkout with no .git, or a CI clone git will
not read falls back to TANGLED_SHA/GITHUB_SHA, without claiming to know whether the tree was
clean, which only git can see; failing that it says on the build log and on the panel that the commit
was not recorded. The Node half lives in scripts/ because vite.config.ts is typechecked by
svelte-check against the browser's tsconfig, which has no @types/node and should not grow one —
scripts/build-stamp.d.mts is the declaration it reads instead, and it imports the shape from
src/lib/build.ts so the injected object and the app cannot disagree.
Verified end to end: the sha reaches the client bundle (commit:"ac22b647…" in
build/_app/immutable/nodes/0.*.js).
Tests #
src/lib/build.test.ts(vitest, pure) — the summary, the fact list, the mono/sans split, and the origin warning including its loopback and no-origin cases.src/lib/components/BuildInfo.svelte.test.ts(vitest, jsdom) — the disclosure is drawn, closed, and carries exactly the facts the module computed.test/build-stamp.test.mjs(node --test) — the stamp's shape, whole-commit-or-none, the two degraded paths driven in subprocesses with git deliberately broken, and that the panel is mounted in both shells and gated by nothing.
Checks #
pnpm lint, pnpm typecheck, pnpm build and pnpm test all pass from the repo root (704 UI tests;
fail 0 in every package). pnpm codegen produces no diff — no lexicon or generated file is touched.
Risks #
- No lexicon, fold, record or daemon code is involved; the protocol surface is unchanged.
- The only pre-existing code touched is
auth.svelte.ts, and only to import theisLoopbackHostit used to define privately — same four hosts, same call site. - In this container git refuses
/workfrom inside the vite process (the mount stats as root-owned there), so builds here fall to the env-sha path. That is an artifact of the sandbox, not of the change: git answers normally from a shell, and the fallback exists precisely for environments like it. Worth a glance at the first deploy log — if the spindle checkout ever hits the same thing, the linebuild stamp: git could not read this checkoutwill say so andTANGLED_SHAwill carry the commit anyway.