Tenant-configurable branding for the secondary screen. URL-field MVP
(operator hosts logo + promo image elsewhere). Admin form at
/cafe/settings/display with live preview;
/cafe/display/[sessionId] idle screen renders the tenant's
logo, primary-color gradient, promo text, and promo image.
cafe.tenant_config
(display_logo_url, display_primary_color,
display_promo_text, display_promo_image_url).
validateBranding normalizes good input (trim + uppercase hex)
and rejects 8 malformed cases (bad hex, missing hash, extra digit,
javascript:, data:, ftp:, oversize
promo, oversize URL). Authed admin route + R1.7 display route + POS routes
all intact.
cafe.tenant_config row to update
(UPDATE on a missing row is a no-op so the round-trip wouldn't prove
much locally). Prod get-coffee has a real config — Gate 2
saves real branding through the action and reads it back via the public
/cafe/display page.
20260424200000_cafe_display_branding —
4 nullable columns on cafe.tenant_config. URL-field
approach for MVP; upload to R2/S3 deferred.cafeTenantConfig gains
displayLogoUrl, displayPrimaryColor,
displayPromoText, displayPromoImageUrl.lib/db/display_branding.ts —
getDisplayBranding (transaction-wrapped to bypass
Hyperdrive's SELECT cache for save-then-reload), saveDisplayBranding,
and validateBranding with hex / URL-scheme / length guards.lib/actions/display_branding.ts —
saveDisplayBrandingAction bound by
nix_cafe.settings.edit, with audit-log entry on every save./cafe/settings/display — two-column
layout: form on the left (logo URL, color picker with swatch, promo
text, promo image URL) + live PreviewIdle on the right.
Read-only when the user lacks settings.edit.app/(public-display)/display/[sessionId]/page.tsx —
loads getDisplayBranding(tenantId) server-side and passes
to DisplayClient. Idle screen renders the logo
(<img> with onError fallback), primary-color
gradient, promo text card, and promo image. Cart + Paid screens use
the primary color too. All branding fields are optional — absent
fields fall back to the existing default theme.cafe.tenant_config schema✓ 4 columns addedLoading…
validateBranding — pure logic✓ 8 bad cases caughtLoading…
Loading…