Skip to content

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.tsxposition:fixed span, 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 in data-* attributes for inspection.
  • Injection: next.config.ts resolves the SHA (BUILD_SHA Docker build-arg → Vercel's VERCEL_GIT_COMMIT_SHA → local git'dev') and inlines it as NEXT_PUBLIC_BUILD_SHA (+ _TIME).
  • NAS: nas-image.yml passes --build-arg BUILD_SHA=${{ github.sha }} + a build timestamp; the Dockerfile promotes them to env JUST BEFORE next build — deliberately AFTER npm ci, so the volatile per-commit SHA can't bust the stable node_modules deps-layer cache (T-073/T-106) and force the NAS to re-pull ~2 GB every deploy.
  • Vercel: reads VERCEL_GIT_COMMIT_SHA automatically (no extra config).
  • Mounted globally: app/layout.tsx (App Router — ~all pages) + a NEW minimal pages/_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.