---
phase: 06.6
slug: uat-accounts-integer-viewport-scaling-chat-region-clamp-right-click-menu
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-05-16
last_updated: 2026-05-16
---

# Phase 06.6 — Validation Strategy

> Per-phase validation contract for feedback sampling during execution.
> Derived from `06.6-RESEARCH.md` §Validation Architecture.
> Revised 2026-05-16 — row 06.6-01-02 updated per R-01 console.log-spy capture strategy.

---

## Test Infrastructure

| Property | Value |
|----------|-------|
| **Framework — server** | Vitest 4.1.5 (`apps/server/package.json:55`) |
| **Framework — client** | Vitest 3.2.4 + Playwright 1.59.1 (`apps/client/package.json:33,27`) |
| **Config file — server** | `apps/server/vitest.config.ts` |
| **Config file — client** | `apps/client/vitest.config.ts` + `apps/client/playwright.config.ts` |
| **Quick run command** | `pnpm --filter @rebno/server test && pnpm --filter @rebno/client test` |
| **Full suite command** | `pnpm -r test && pnpm --filter @rebno/server test:integration && pnpm --filter @rebno/client test:e2e` |
| **Estimated runtime — quick** | ~30 seconds |
| **Estimated runtime — full** | ~2-3 minutes |

---

## Sampling Rate

- **After every task commit:** `pnpm --filter @rebno/server test && pnpm --filter @rebno/client test` (unit + jsdom — sub-30s)
- **After every plan wave:** `pnpm --filter @rebno/server test:integration && pnpm --filter @rebno/client test:e2e` (integ + local Playwright — 2-3 min)
- **Before `/gsd-verify-work`:** Full suite green (`pnpm -r test`); cli-08 e2e GREEN against local + post-deploy GREEN against staging
- **Max feedback latency:** 30 seconds (quick) / 180 seconds (full)

---

## Per-Task Verification Map

| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| 06.6-01-01 | 01 | 0 | REQ-DEP-01 | T-06.6-AUTH-01 | Seed migration creates `dunsen_uat` + `rebbie_uat` rows idempotently with argon2id-hashed password generated from `crypto.randomBytes(24).toString('base64url')` (R-01 — no env reads) | integration | `pnpm --filter @rebno/server vitest run test/seed-uat.integ.test.ts -t "seeds dunsen_uat and rebbie_uat"` | ❌ W0 | ⬜ pending |
| 06.6-01-02 | 01 | 0 | REQ-DEP-01, REQ-CLI-03 | T-06.6-AUTH-02 | Seed-produced random hash verifies via Better-Auth sign-in roundtrip — plaintext captured from console.log spy (no env preset) | integration | `pnpm --filter @rebno/server vitest run test/seed-uat.integ.test.ts -t "captured plaintext signs in"` | ❌ W0 | ⬜ pending |
| 06.6-01-03 | 01 | 0 | REQ-DEP-01 | T-06.6-AUTH-03 | Migration is a no-op on second run — `consoleLogSpy.mock.calls.length === 0` asserts silence (no row mutation, no stdout password print) | integration | `pnpm --filter @rebno/server vitest run test/seed-uat.integ.test.ts -t "no-op re-run does NOT call console.log"` | ❌ W0 | ⬜ pending |
| 06.6-01-04 | 01 | 0 | REQ-DEP-01 | T-06.6-AUTH-01 | `account.password` is `$argon2id$` PHC hash | integration | `pnpm --filter @rebno/server vitest run test/seed-uat.integ.test.ts -t "argon2id"` | ❌ W0 | ⬜ pending |
| 06.6-02-01 | 02 | 0 | REQ-CLI-01, REQ-CLI-06 | — | `computeIntegerZoom(w,h)` returns Math.floor(min(w/640, h/480)) with floor of 1 | unit | `pnpm --filter @rebno/client vitest run src/__test__/integer-zoom.test.ts` | ❌ W0 | ⬜ pending |
| 06.6-02-02 | 02 | 0 | REQ-CLI-01, REQ-CLI-06 | — | `computeIntegerZoom(0,0)` returns 1 (defensive NaN/zero guard for headless envs) | unit | `pnpm --filter @rebno/client vitest run src/__test__/integer-zoom.test.ts -t "defensive"` | ❌ W0 | ⬜ pending |
| 06.6-03-01 | 03 | 0 | REQ-CLI-05 | — | `canvas-rect-tracker` applies left/top/width/height from mocked getBoundingClientRect | unit | `pnpm --filter @rebno/client vitest run src/__test__/canvas-rect-tracker.test.ts` | ❌ W0 | ⬜ pending |
| 06.6-03-02 | 03 | 0 | REQ-CLI-05 | — | Tracker coalesces multiple events fired within one animation frame via rAF | unit | `pnpm --filter @rebno/client vitest run src/__test__/canvas-rect-tracker.test.ts -t "coalesce"` | ❌ W0 | ⬜ pending |
| 06.6-03-03 | 03 | 1 | REQ-CLI-05, REQ-CLI-06 | — | ChatHUD repositions to canvas rect on Phaser SCALE.RESIZE event (jsdom-driven) | unit | `pnpm --filter @rebno/client vitest run src/__test__/chat-hud.test.ts -t "canvas-rect clamp"` | ✅ (extend) | ⬜ pending |
| 06.6-03-04 | 03 | 1 | REQ-CLI-05 | — | ChatHUD root container remains under `#dom-overlay` (NOT migrated into `#game-root`) — ADR 0008 HARD invariant assertion | unit | `pnpm --filter @rebno/client vitest run src/__test__/chat-hud.test.ts -t "dom-overlay invariant"` | ✅ (extend) | ⬜ pending |
| 06.6-04-01 | 04 | 0 | REQ-CLI-02, REQ-CLI-03 | T-06.6-UI-01 | EscMenu opens on right-click (`pointer.rightButtonDown()`) ONLY, not left-click | unit | `pnpm --filter @rebno/client vitest run src/__test__/esc-menu.test.ts -t "right-click"` | ✅ (extend) | ⬜ pending |
| 06.6-04-02 | 04 | 0 | REQ-CLI-02 | T-06.6-UI-02 | Left-click on canvas is a no-op (no menu, no preventDefault) | unit | `pnpm --filter @rebno/client vitest run src/__test__/esc-menu.test.ts -t "left-click no-op"` | ✅ (extend) | ⬜ pending |
| 06.6-04-03 | 04 | 0 | REQ-CLI-02 | T-06.6-UI-02 | `this.input.mouse.disableContextMenu()` called in GameScene.create — browser contextmenu suppressed on canvas only | unit | `pnpm --filter @rebno/client vitest run src/__test__/esc-menu.test.ts -t "disableContextMenu"` | ✅ (extend) | ⬜ pending |
| 06.6-04-04 | 04 | 0 | REQ-CLI-02 | — | ESC keydown handler at GameScene.ts:349-355 untouched (regression assertion via ESM-safe `fileURLToPath(import.meta.url)` path resolution) | unit | `pnpm --filter @rebno/client vitest run src/__test__/esc-menu.test.ts -t "ESC keydown"` | ✅ (regression) | ⬜ pending |
| 06.6-05-01 | All | 2 | REQ-CLI-08 | — | Two-client smoke (dev-bypass uat_a/uat_b unchanged per R-02) — no regression | e2e | `pnpm --filter @rebno/client test:e2e -- -g "CLI-08 hard milestone"` | ✅ (regression) | ⬜ pending |

*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*

---

## Wave 0 Requirements

- [ ] `apps/server/test/seed-uat.integ.test.ts` — new integration suite covering REQ-DEP-01 extension (4 sub-tests: idempotency + console.log spy capture, sign-in roundtrip via captured plaintext, no-op re-run with zero console.log calls assertion, argon2id hash format). **R-01 enforcement: tests use `vi.spyOn(console, 'log')` to capture the random plaintext — NO `process.env.UAT_PASSWORD_*` preset.**
- [ ] `apps/client/src/__test__/integer-zoom.test.ts` — new unit suite for pure `computeIntegerZoom(w,h)` (mocks `window.innerWidth`/`innerHeight`)
- [ ] `apps/client/src/__test__/canvas-rect-tracker.test.ts` — new unit suite for the canvas-rect tracker (mocks `canvas.getBoundingClientRect`, fires resize events, asserts single-rAF coalesced apply)
- [ ] Extend `apps/client/src/__test__/chat-hud.test.ts` — add canvas-rect clamp test + ADR-0008 invariant regression
- [ ] Extend `apps/client/src/__test__/esc-menu.test.ts` — replace left-click EscMenu assertions with right-click; add left-click-no-op + disableContextMenu coverage. **ESM-safe `__dirname` derivation via `fileURLToPath(import.meta.url)` for the file-content regression test.**
- [ ] No framework install needed; all test infrastructure already present.

---

## Manual-Only Verifications

| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Operator captures stdout-printed UAT password on first staging deploy | REQ-DEP-01 (R-01) | Random password generation is migration-time stdout output; cannot be asserted in CI without leaking the value (CI uses a console.log spy in-process; staging has no spy — only flyctl logs) | After first staging migration run: tail `flyctl logs` for `[seed-uat] account_seeded username=dunsen_uat password=<plaintext>` and `[seed-uat] account_seeded username=rebbie_uat password=<plaintext>`; store both plaintexts in personal password manager |
| Operator opens app on three window sizes (1920×1080, 1280×720, 800×600); confirms integer zoom + chat clamps to canvas + right-click opens EscMenu | REQ-CLI-01 / REQ-CLI-05 / REQ-CLI-06 | Visual layout + cross-window-size scaling requires human eye | Document in `apps/client/06-HUMAN-UAT.md` — three window sizes; expected zooms 3/2/1; chat HUD inside letterbox; right-click opens EscMenu; left-click no-op; browser contextmenu suppressed on canvas, present on chat-input |
| Operator logs into staging as `dunsen_uat` + `rebbie_uat` in parallel with Playwright dev-bypass UAT — confirms no collision | R-02 | Multi-session parallel UAT is by definition out-of-CI | Open two browser windows on staging; sign in as `dunsen_uat` and `rebbie_uat` while CI is running cli-08 smoke; confirm all four characters visible + chat works without crosstalk |

---

## Validation Sign-Off

- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < 180s (full)
- [ ] `nyquist_compliant: true` set in frontmatter

**Approval:** pending
