/** * The screen payload contract. * * This is the frozen boundary of the whole system: the resolver fills it, the * renderer reads it, and a snapshot test pins it. Every string the panel shows * is computed here, server-side — the renderer performs no business logic, it * only draws. That is what makes the display testable without a device. * * Bump SCREEN_SCHEMA_VERSION when the shape changes, never silently. */ export const SCREEN_SCHEMA_VERSION = 1; /** * How much free text the bottom banner can hold before it stops fitting. * Shared by the message editor's counter and the renderer, so the warning the * user sees and the space actually available can never drift apart. */ export const MESSAGE_MAX_CHARS = 120; export type ScreenStatus = 'OPEN' | 'CLOSED' | 'CLOSING_SOON' | 'OPENING_SOON'; /** * Where the banner text came from: something a person wrote, or the automatic * notice raised when the week departs from the reference schedule. */ export type ScreenBannerKind = 'MESSAGE' | 'SCHEDULE_TODAY' | 'SCHEDULE_WEEK'; export type ScreenToday = { date_fr: string; date_en: string; status: ScreenStatus; /** The single word shown in very large type. */ status_fr: string; status_en: string; /** "10:00 – 13:00 · 14:00 – 18:30", empty when closed all day. */ hours_text: string; /** "Ferme à 18:30" / "Ouvre demain à 10:00", empty when there is nothing ahead. */ next_change_fr: string; next_change_en: string; is_exception: boolean; }; export type ScreenWeekDay = { label_fr: string; label_en: string; hours_text: string; is_today: boolean; is_closed: boolean; is_exception: boolean; }; export type ScreenBanner = { kind: ScreenBannerKind; text_fr: string; text_en: string; }; export type ScreenPayload = { schema: number; /** ISO 8601 with the shop's UTC offset, e.g. "2026-09-20T10:15:00+02:00". */ generated_at: string; shop_name: string; logo_url: string; today: ScreenToday; /** Always seven entries, Monday through Sunday of the current week. */ week: ScreenWeekDay[]; /** `null` when the shop has nothing to say, so the week block takes the room. */ banner: ScreenBanner | null; }; /** * Automatic banner wording. Pre-translated on purpose: these lines must never * depend on the translation service being reachable. */ export const AUTO_BANNERS: Record<'SCHEDULE_TODAY' | 'SCHEDULE_WEEK', { fr: string; en: string }> = { SCHEDULE_TODAY: { fr: "Changement d'horaire aujourd'hui", en: 'Opening hours changed today', }, SCHEDULE_WEEK: { fr: 'Horaires exceptionnels cette semaine', en: 'Unusual opening hours this week', }, };