Always-on build watermark — verify the live build at a glance
Why¶
The NAS deploy is unstable (cellular link I-009; deploy starvation/retry T-105), so "is the web app actually on the build I just pushed?" has been hard to answer with confidence. The owner asked for a subtle, always-visible build identifier on EVERY page (bottom-right, watermark-style) that no code change can leave stale.
Owner request (verbatim, 2026-06-24): "is it possible that we keep a floating build number displayed subtly at all times on EVERY PAGE that won't be wiped away by any code changes on the bottom right corner? Like a watermark?"
What shipped¶
A fixed, bottom-right build stamp showing the short git SHA the running bundle was compiled from — auto-derived at BUILD time, never hand-maintained, so every build stamps its OWN SHA and it can never go stale (that's the "won't be wiped away").
components/BuildWatermark.tsx—position:fixedspan, bottom-right, mid-grey low-opacity (reads on light + dark),pointer-events:none(never intercepts a click), max z-index (visible over modals). Full SHA + build time also indata-*attributes for inspection.- Injection:
next.config.tsresolves the SHA (BUILD_SHADocker build-arg → Vercel'sVERCEL_GIT_COMMIT_SHA→ localgit→'dev') and inlines it asNEXT_PUBLIC_BUILD_SHA(+_TIME). - NAS:
nas-image.ymlpasses--build-arg BUILD_SHA=${{ github.sha }}+ a build timestamp; the Dockerfile promotes them to env JUST BEFOREnext build— deliberately AFTERnpm ci, so the volatile per-commit SHA can't bust the stablenode_modulesdeps-layer cache (T-073/T-106) and force the NAS to re-pull ~2 GB every deploy. - Vercel: reads
VERCEL_GIT_COMMIT_SHAautomatically (no extra config). - Mounted globally:
app/layout.tsx(App Router — ~all pages) + a NEW minimalpages/_app.tsx(the 2 remaining Pages Router viewer pages:/v/[id],/view/document/[token]). The NextAuth sign-in screen renders OUTSIDE the app shell, so it's the one page without the stamp (flagged to the owner).
Verified¶
tsc clean. Dev server boots clean with the new next.config.ts. The compiled .next SSR chunk was
confirmed to contain the watermark element (data-build-sha + z-index 2147483647) WITH the SHA inlined
as a string literal — i.e. the env injection AND the component both work end to end. (The live visual
couldn't be auto-screenshotted: every reachable unauthenticated route redirects to NextAuth, which bypasses
the app shell — so owner confirmation on the deployed build is the last mile.)
Decision log¶
- ✅ Attestation: read AGENTS.md + checked the board by scope — distinct from T-105 (deploy retry) and T-106 (transfer size); this is a verification surface, no existing task covered it. Source: EOP Local Assistance (fork) · session edb0abc7-6373-449b-8e31-379fd38d391b (local Claude Code). Proposed + approved by the owner (quoted above), 2026-06-24.
Blast radius: adds a global element to every page via app/layout.tsx + a NEW pages/_app.tsx
(previously absent — minimal, renders the page exactly as Next's default App did, no global CSS/providers,
so the 2 viewer pages are unaffected). next.config.ts gains an env block + a build-time git call
(guarded; skipped when a build-arg/Vercel var is present). No runtime or data behaviour changes.
2026-06-24 — renumbered T-107 → T-108 on merge (UID collision)¶
Opened this session as T-107, but another agent's task (RBAC legacy-role cleanup) merged to main
first and took T-107 (the README next-free had advanced to T-108). Per the AGENTS.md "whoever merges
second renumbers" rule, this task moved to the real next-free T-108 before pushing — the file, its
uid, and every [T-108] marker in the code (next.config.ts, Dockerfile, nas-image.yml,
BuildWatermark.tsx, app/layout.tsx, pages/_app.tsx) were renumbered in one go, and the README
next-free bumped to T-109. Their T-107 was left untouched.
Source (edit): EOP Local Assistance (fork) · session edb0abc7-6373-449b-8e31-379fd38d391b.