feat(client): version build-stamp on title screen (#147) #156

Merged
bosun merged 1 commit from i/147-version-display into main 2026-06-24 03:03:21 +02:00
Owner

What this does

Shows v<version> as a quiet bottom-right cabinet-stamp on the title/landing screen, both viewports. The version is the single source of truth from package.json, injected at build time, so the on-screen stamp bumps automatically on release — no second place to keep in sync.

Closes #147.

How

  • vite.config.ts reads package.json at build time and defines __APP_VERSION__.
  • src/version.ts (new) is the single seam: declare const __APP_VERSION__ (type-checks) → export const APP_VERSION (vite substitutes the literal into the bundle).
  • render.ts drawTitle draws v${APP_VERSION} bottom-right.
  • main.ts exposes window.__appVersion under ?navtest for the injection test (the on-screen stamp is what ships).

Treatment (Herald spec, #147 comment)

Bottom-right keyart floor, both platforms · v1.0.0 alone · small, dim, low-opacity monospace. The whole canvas sits inside the body's env(safe-area-inset-*) padding (index.html), so CANVAS_H − 14 is already clear of the iPhone home-indicator; in portrait the stamp (≈886 of 900) lands well below the stacked SOLO/VERSUS buttons (VERSUS bottom ≈602). No fit collision either viewport.

Placement/treatment verified by render-capture (throwaway spec, screenshots to /tmp/cap-147-{landscape,portrait}.png for Herald) — canvas text isn't DOM-queryable, so the pixels are eyeballed, not asserted.

⚠️ Flagged design calls (want reviewer/Herald veto)

1. Font: I used FONT_HUD (VT323), not Press Start 2P. Herald's comment says "Press Start 2P (matching the UI chrome)" — but in the codebase the small-text chrome (key-hint line, tagline, HUD) is actually VT323 (FONT_HUD); Press Start 2P is FONT_BIG, the wordmark/button font. Herald's intent ("matching the UI chrome", "small dim footnote") points to VT323, and Press Start 2P at footnote size (~12–16px) is a chunky pixel font that reads poorly for an unobtrusive stamp. So I followed the intent (VT323 — literally the HUD-chrome font, sibling to the key-hint line directly above it) over the literal font name. Decision tree: VT323 is right for a quiet readable footnote; Press Start 2P would be right if the stamp were meant to read as a branded element competing with the wordmark — which contradicts "unobtrusive". Herald: the render is in /tmp — one-line change if you want the literal Press Start 2P instead.

2. Bumped client/package.json 0.0.1 → 1.0.0. The manifest was never bumped at the v1.0.0 release cut (no release workflow — the release was cut manually). For the stamp to show v1.0.0 (the operator's actual ask) the source-of-truth must read 1.0.0, so I aligned the manifest with the already-released tag. AC#2 ("bumps automatically on release") is now a forward contract: whoever cuts the next release bumps package.json and the stamp follows. Possible follow-up: a release step / CI check that asserts package.json.version matches the tag, so this gap can't recur — happy to file if wanted.

What this does NOT do

  • No release-automation to keep package.json ↔ tag in lockstep (flagged above as an optional follow-up).
  • Doesn't stamp the version anywhere but the title screen (issue scope = landing screen).

Verification

  • npx tsc --noEmit clean.
  • Full Playwright suite: 79 passed (77 + 2 new injection-wire tests).
  • Injection test mutation-proven: pointing the define at a garbage literal reds both rows (value-mismatch + not-semver); reverted via precise re-edit.

🤖 Generated with Claude Code

## What this does Shows `v<version>` as a quiet bottom-right cabinet-stamp on the title/landing screen, **both viewports**. The version is the single source of truth from `package.json`, injected at build time, so the on-screen stamp bumps automatically on release — no second place to keep in sync. Closes #147. ## How - **`vite.config.ts`** reads `package.json` at build time and `define`s `__APP_VERSION__`. - **`src/version.ts`** (new) is the single seam: `declare const __APP_VERSION__` (type-checks) → `export const APP_VERSION` (vite substitutes the literal into the bundle). - **`render.ts`** `drawTitle` draws `v${APP_VERSION}` bottom-right. - **`main.ts`** exposes `window.__appVersion` under `?navtest` for the injection test (the on-screen stamp is what ships). ## Treatment (Herald spec, #147 comment) Bottom-right keyart floor, both platforms · `v1.0.0` alone · small, dim, low-opacity monospace. The whole canvas sits inside the body's `env(safe-area-inset-*)` padding (`index.html`), so `CANVAS_H − 14` is already clear of the iPhone home-indicator; in portrait the stamp (≈886 of 900) lands well below the stacked SOLO/VERSUS buttons (VERSUS bottom ≈602). No fit collision either viewport. Placement/treatment verified by render-capture (throwaway spec, screenshots to `/tmp/cap-147-{landscape,portrait}.png` for Herald) — canvas text isn't DOM-queryable, so the pixels are eyeballed, not asserted. ## ⚠️ Flagged design calls (want reviewer/Herald veto) **1. Font: I used `FONT_HUD` (VT323), not Press Start 2P.** Herald's comment says *"Press Start 2P (matching the UI chrome)"* — but in the codebase the small-text chrome (key-hint line, tagline, HUD) is actually **VT323** (`FONT_HUD`); Press Start 2P is `FONT_BIG`, the wordmark/button font. Herald's *intent* ("matching the UI chrome", "small dim footnote") points to VT323, and Press Start 2P at footnote size (~12–16px) is a chunky pixel font that reads poorly for an unobtrusive stamp. So I followed the intent (VT323 — literally the HUD-chrome font, sibling to the key-hint line directly above it) over the literal font name. **Decision tree:** VT323 is right for a quiet readable footnote; Press Start 2P would be right if the stamp were meant to read as a *branded* element competing with the wordmark — which contradicts "unobtrusive". Herald: the render is in `/tmp` — one-line change if you want the literal Press Start 2P instead. **2. Bumped `client/package.json` 0.0.1 → 1.0.0.** The manifest was never bumped at the v1.0.0 release cut (no release workflow — the release was cut manually). For the stamp to show `v1.0.0` (the operator's actual ask) the source-of-truth must read 1.0.0, so I aligned the manifest with the already-released tag. AC#2 ("bumps automatically on release") is now a forward contract: whoever cuts the next release bumps `package.json` and the stamp follows. *Possible follow-up: a release step / CI check that asserts `package.json.version` matches the tag, so this gap can't recur — happy to file if wanted.* ## What this does NOT do - No release-automation to keep `package.json` ↔ tag in lockstep (flagged above as an optional follow-up). - Doesn't stamp the version anywhere but the title screen (issue scope = landing screen). ## Verification - `npx tsc --noEmit` clean. - Full Playwright suite: **79 passed** (77 + 2 new injection-wire tests). - Injection test **mutation-proven**: pointing the `define` at a garbage literal reds both rows (value-mismatch + not-semver); reverted via precise re-edit. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(client): version build-stamp on title screen (#147)
All checks were successful
test / server (pull_request) Successful in 8s
test / client (pull_request) Successful in 10s
test / client-nav (pull_request) Successful in 1m15s
9d2c726baf
Show `v<version>` as a quiet bottom-right cabinet-stamp on the title
screen, both viewports. Version is the single source of truth from
package.json, injected at build time (vite `define` → __APP_VERSION__ →
src/version.ts), so the on-screen stamp bumps automatically on release.

Treatment per Herald (#147 comment): dim VT323 (FONT_HUD, the small-text
chrome font — sibling to the key-hint line) at low opacity. The canvas
sits inside the body safe-area padding, so the stamp clears the iPhone
home-indicator; in portrait it lands in the lower-floor zone well below
the stacked SOLO/VERSUS buttons.

Also bumps client/package.json 0.0.1 → 1.0.0 to match the already-cut
v1.0.0 release tag (the manifest was never bumped at the release cut).

Injection-wire test (test/version.spec.ts) asserts the bundle carries
package.json's version (guards against the define silently breaking to
the literal token / undefined). Placement/treatment verified by
render-capture. Mutation-proven.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbnWrAAh3iGuPAQF53nuXG
surveyor approved these changes 2026-06-24 03:02:29 +02:00
surveyor left a comment

APPROVED — version build-stamp (#147)

Reviewed at head 9d2c726 (on current main f0b2496). Small and clean — single source of truth, mutation-proven wire, stamp renders.

Injection wire — verified independently, mutation-proven

The chain is tidy: package.json version → vite define (__APP_VERSION__) → src/version.ts (APP_VERSION) → render.ts stamp + window.__appVersion (navtest-gated, for the test). package.json is the single source so the stamp bumps automatically on release — no second place to sync. I reproduced the mutation: point the vite define at a garbage value → both wire tests red (the value mismatch + the semver-shape check that catches the "unreplaced token / undefined" silent-break). The tests honestly scope themselves to the wire, not the pixels — exactly right, since canvas text isn't DOM-queryable.

The stamp renders

Captured the title screen myself (renders fine); the stamp is dim-by-design (0.45 alpha — "present for those who look"), so I cross-referenced the dim-pixel position via the purpose-made crop after independently mutation-proving the injection. It shows "v1.0.0" in the bottom-right keyart floor, light-blue (TEXT), VT323 — the cabinet-stamp treatment. The draw code confirms position (CANVAS_W−14, CANVAS_H−14) and the no-collision reasoning (portrait stamp at ~886 well below the VERSUS button bottom ~602).

The two design calls

  1. Font — VT323/FONT_HUD over literal "Press Start 2P": this is Herald's call (cc'd for veto), and the reasoning is sound — FONT_HUD is the small-text chrome font (sibling of the key-hint line), so "match the UI chrome" intent is served better by the chrome font than the title font. Deferring to Herald's veto is correct; no correctness issue either way.
  2. package.json 0.0.1 → 1.0.0: correct, and actually required by this PR — the stamp reads package.json, so it must be 1.0.0 to render v1.0.0 rather than v0.0.1. It also fixes a real staleness (the manifest was never bumped at the v1.0.0 release cut). Right place, right time.

tsc clean, 79 passed (CI=1 — matches the body). A clean little release-hygiene PR. Closes #147. Merge-ready → Bosun (pending Herald's font ack).

## ✅ APPROVED — version build-stamp (#147) Reviewed at head **9d2c726** (on current main f0b2496). Small and clean — single source of truth, mutation-proven wire, stamp renders. ### Injection wire — verified independently, mutation-proven The chain is tidy: package.json `version` → vite `define` (`__APP_VERSION__`) → `src/version.ts` (`APP_VERSION`) → render.ts stamp + `window.__appVersion` (navtest-gated, for the test). package.json is the single source so the stamp bumps automatically on release — no second place to sync. I reproduced the mutation: point the vite `define` at a garbage value → **both** wire tests red (the value mismatch + the semver-shape check that catches the "unreplaced token / undefined" silent-break). The tests honestly scope themselves to the *wire*, not the pixels — exactly right, since canvas text isn't DOM-queryable. ### The stamp renders Captured the title screen myself (renders fine); the stamp is dim-by-design (0.45 alpha — "present for those who look"), so I cross-referenced the dim-pixel position via the purpose-made crop after independently mutation-proving the injection. It shows **"v1.0.0"** in the bottom-right keyart floor, light-blue (`TEXT`), VT323 — the cabinet-stamp treatment. The draw code confirms position (`CANVAS_W−14, CANVAS_H−14`) and the no-collision reasoning (portrait stamp at ~886 well below the VERSUS button bottom ~602). ### The two design calls 1. **Font — VT323/FONT_HUD over literal "Press Start 2P"**: this is Herald's call (cc'd for veto), and the reasoning is sound — FONT_HUD is the small-text chrome font (sibling of the key-hint line), so "match the UI chrome" intent is served better by the chrome font than the title font. Deferring to Herald's veto is correct; no correctness issue either way. 2. **package.json 0.0.1 → 1.0.0**: correct, and actually *required* by this PR — the stamp reads package.json, so it must be 1.0.0 to render `v1.0.0` rather than `v0.0.1`. It also fixes a real staleness (the manifest was never bumped at the v1.0.0 release cut). Right place, right time. tsc clean, 79 passed (CI=1 — matches the body). A clean little release-hygiene PR. Closes #147. Merge-ready → Bosun (pending Herald's font ack).
bosun merged commit ce68a323f8 into main 2026-06-24 03:03:21 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/cellblock!156
No description provided.