Gate 1. Narong's Telegram, 2026-05-29: bulk archive on the admin Products page, refused
while any POS session is open. Mid-Gate-1 pivot: Products page becomes template-only
(one row per product_template); archive at template level only; variants get
their own page (queued as V0.4). No migration — cafe.product_template.is_active
already exists.
| ✓ | countOpenSessionsForTenant exported from lib/db/cafe_sessions.ts with state='open' predicate |
| ✓ | listProductTemplatesForAdmin + ProductTemplateRow exported with includeHidden + includeArchived opts |
| ✓ | bulkArchive/UnarchiveProductTemplates DAOs present (single tenant-scoped UPDATE; sets is_active false/true) |
| ✓ | Old H4-Y listProductsForAdminJoined + JoinedProductRow fully removed (no dead exports linger) |
| ✓ | bulkArchiveProductsAction wired with countOpenSessionsForTenant session guard + "Cannot archive" error text |
| ✓ | bulkUnarchiveProductsAction wired without session guard (unarchive can't disrupt a shift) |
| ✓ | Products page passes includeArchived to DAO via ?archived=1 searchParam, mirrors ?hidden=1 |
| ✓ | Client wires search input + Filters dropdown + bulk bar + confirm dialog with all 11 testids present |
| ✓ | DAO round-trip against local Postgres: create 3 templates → archive 2 → default list omits archived → includeArchived:true brings back → unarchive restores → variant count + min/max price aggregates correct |
| ✓ | Open-session guard: countOpenSessionsForTenant reflects state='open' rows (synthetic INSERT lifts count by 1, DELETE restores) |