2026-04-29 on get-coffee. First sub-phase of R4 (Cpanel finish). Cpanel shop CRUD has shipped since 04-20 but had never been exercised end-to-end against a multi-shop tenant. R4.1's job: drive the full activation path on prod (admin login → create 2nd shop → cashier-side picker switch → Cpanel disable → picker collapse) and surface any blockers. Two real findings landed.
admin_operators ended up empty → admin login returned 401 for everyone for ~24h. Fixed out-of-band by inserting one operator row (id=1, email admin@nixoutdoor.com, role super_admin, password unchanged from prior prod). Open follow-up: add an idempotent admin seed to nix-outdoor-sales-backend/migrate.js so the next fresh DB doesn't repeat this.is_active. Cpanel "delete shop" is a soft-disable (sets is_active=false, preserves the row), but nix-cafe/lib/db/shops.ts::listAccessibleShops read shops by tenant_id only — soft-disabled shops still appeared in the cashier picker, the commerce ShopSelector, and the daily-report scope filter. Fix: and(eq(tenantId, …), eq(isActive, true)).is_active filter, the picker would lag a minute behind a Cpanel disable until the cache expired. Wrapped listAccessibleShops in db.transaction() per project_hyperdrive_cache_stale_reads.md — bypasses the cache so ops disables reflect on the cashier UI immediately.POST /admin/tenants/:id/shops created the "annex" shop; DB ground truth confirmed 2 shops./cafe/pos, owner with 2 active shops sees register-shop-picker dropdown (single-shop label gone).DELETE via admin API → DB row keeps is_active=false; /cafe/pos picker collapses back to single-shop label without the 60s Hyperdrive cache lag.
| test-r4-1-prod.mjs (NEW) | 9/9 |
| test-phase1-prod.mjs | 11/11 |
| test-receipt-preview-prod.mjs | 6/6 |
| test-r2-followups3-prod.mjs | 7/7 |
| test-r2-followups2-prod.mjs | 11/11 |
| test-r2-followups-prod.mjs | 19/19 |
| test-nix-os-r2-4b-prod.mjs | 12/12 |
| test-nix-os-70-2-prod.mjs | 10/10 |
| test-cafe-followups-prod.mjs | 5/5 |
nix-cafe @ b553925 (cumulative across fa6b288 + b553925):
lib/db/shops.ts — listAccessibleShops now filters
is_active=true AND is wrapped in
db.transaction() to bypass Hyperdrive
cache. Soft-disabled shops disappear
from picker / commerce selector /
daily-report scope filter immediately
after Cpanel disable.
prod data fix (no commit, one-shot script):
admin_operators(id=1, email='admin@nixoutdoor.com', role='super_admin')
— restored after Supabase migration left table empty.
tests:
test-r4-1-prod.mjs — NEW (9 checks). Drives admin API +
Cafe UI end-to-end for multi-shop
activation + soft-disable propagation.
R4.1 ─ Multi-shop activation E2E ✓ shipped (2026-04-29) R4.2 ─ Impersonation pending R4.3 ─ Per-product usage endpoint pending R4.4 ─ Drop legacy tenant_limits/plan_code pending (last) Open follow-ups discovered by R4.1: - admin seed in migrate.js (so future fresh DBs don't repeat the lockout) - audit other shop reads (kitchen, cashiers, pin_identities) for is_active filter