A collaborative coding-agent orchestrator for atproto radl.app

put build info in UI for debugging #121

Merged disnetdev.com
targeting main from radial/impl-bdb59e3dc0c1
Merged opened by and assigned to disnetdev.com
targeting main from radial/impl-bdb59e3dc0c1

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. isLoopbackHost moved into client-metadata.ts and is now shared with auth.svelte.ts, so a preview on 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.mjs is 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 the isLoopbackHost it used to define privately — same four hosts, same call site.
  • In this container git refuses /work from 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 line build stamp: git could not read this checkout will say so and TANGLED_SHA will carry the commit anyway.

Radial artifact

Commits 0