2026-05-25 Gate 2 ship — 4th slice of the day. Three Customer Display changes from Narong's Test-Run #3 doc red highlights. Migration applied to prod Supabase; full settings save → reload round-trip verified on lumiere-coffee.
Before: idle stacked logo / storeName / Welcome / promoText / promo-image vertically — busy and the promo was small.
After: when promoImageUrl OR slideshowEnabled + slideshowImages is set, render edge-to-edge promo (no chrome). Falls back to the legacy branded layout when no promo content is set. New fullBleed prop on the Slideshow component (defaults false for back-compat).
Before: while the cashier rings up items, the customer display rendered a full-width cart.
After: when a portrait promo image is configured, the cart screen splits 50/50 — cart column on the left (header + lines + total footer), portrait promo on the right (object-contain on black). No portrait set → falls back to full-width cart. data-split="true"/"false" testid attribute exposes the active mode.
Settings UI: new "Portrait promo image URL" field in Settings > Customer Display, directly under the existing landscape "Promo image URL" field. Same UploadButton component. Hint copy: "Portrait image shown on the right half of the customer display DURING CHECKOUT (cart visible on the left). Use a vertical image (e.g. 1080×1920). Leave blank to skip."
Schema: 1 new col cafe.tenant_config.display_promo_portrait_url VARCHAR(1024), nullable. Migration applied to prod Supabase post-push.
Future expansion: stored as a single VARCHAR for now. If Narong later wants portrait slideshow rotation (like the existing landscape one), it's a small 2-col follow-up migration — same shape as the existing landscape slideshow path.
| ✓ | Prod: cafe.tenant_config has display_promo_portrait_url |
| ✓ | Snapshot current display_promo_portrait_url for restore |
| ✓ | DAO probe: getDisplayBranding + saveDisplayBranding round-trip promoPortraitUrl on prod |
| ✓ | Login to lumiere-coffee |
| ✓ | /cafe/settings/display renders new Portrait promo field |
| ✓ | Settings: fill + save the portrait URL |
| ✓ | Reload settings → portrait URL persisted via prod Supabase round-trip |
| ✓ | Settings: clear the portrait URL + save |
| ✓ | Reload → portrait URL cleared (null on DB) |
| ✓ | Restore original portrait URL |
| ✓ | No 5xx HTTP responses during the suite |
Lumiere's original portrait URL value snapshotted at start, restored at the end via SQL (idempotent — also restored on process.exit).
| test-phase1-prod.mjs | 11/11 (narongix) |
| test-phase2-sso-outdoor-prod.mjs | 6/6 (narongix) |
| test-phase2-cafe-multishop-prod.mjs | 6/6 (demo) · solo run per feedback_phase2_cafe_multishop_solo_retry — first-attempt green (4th time today) |
| test-m1-prod.mjs | 10/10 (narongix) |
| test-r7-prod.mjs | 14/14 (narongix + lumiere) |
| test-r8-prod.mjs | 4/4 (narongix) |
| test-u3-prod.mjs | 11/11 (lumiere — this slice) |