๐Ÿงญ Overview

  • Full-screen 16:9 dark-theme signage board at /tv/
  • Designed to be displayed on in-venue TV screens; blocks mobile viewports with a friendly message
  • Pulls live data from /api/events every 2 minutes; re-evaluates event status client-side every minute without a network request
  • Hard-refresh fallback every 5 minutes via <meta http-equiv="refresh">

Sources: public/tv/index.html, public/tv/script.js, public/tv/styles.css


๐Ÿ–ฅ๏ธ Layout

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  HEADER โ€” I-House Pub logo ยท date ยท live clock      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                          โ”‚                          โ”‚
โ”‚  LEFT (3fr)              โ”‚  RIGHT (2fr)             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ ๐ŸŒŸ Featured Event  โ”‚  โ”‚  โ”‚ ๐Ÿ“… Coming Up       โ”‚  โ”‚
โ”‚  โ”‚  (Happening at     โ”‚  โ”‚  โ”‚   (this week,      โ”‚  โ”‚
โ”‚  โ”‚   the Pub)         โ”‚  โ”‚  โ”‚    past events     โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚  โ”‚    filtered out)   โ”‚  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ”‚ ๐Ÿ“ข Announcements   โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚  (auto-scrolling)  โ”‚  โ”‚  โ”‚ ๐Ÿบ Pub Hours       โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚  โ”‚   (7-day grid)     โ”‚  โ”‚
โ”‚                          โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  FOOTER โ€” Pub Tender ยท Serving Hours ยท Shift State  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Sources: public/tv/index.html, public/tv/styles.css (.tv-main, .tv-left, .tv-aside)


๐Ÿงฎ Event Inclusion Pipeline

The TV page does not query Notion directly. It renders whatever the worker returns from GET /api/events.

Both event arrays use the same base gates:

  • Include on Pub TV must be checked
  • Location must contain "pub" (case-insensitive)
  • Events are sorted by actual start timestamp before the API response is returned

todayEvents (drives โ€œHappening at the Pubโ€)

  • The worker computes todayISO in America/New_York
  • The event day starts at 12:00 AM Eastern
  • The worker queries a broad Notion window from todayISO through < todayISO + 2 days
  • It then narrows that result set to rows whose Event Start falls on todayISO in Eastern time

That extra +2 days query window is intentional. It prevents late-evening Eastern events from being dropped when their timestamp crosses into the next UTC date.

weekEvents (drives โ€œComing Up at the Pubโ€)

  • Uses the same pub-location and TV-include gates
  • Pulls rows with Event Start >= tomorrowISO and < todayISO + 8 days
  • This means calendar tomorrow through calendar day +7

The separation between the two panels happens in the worker. The client does not deduplicate the arrays against each other.

Sources: worker.js (fetchFromNotion, isAtPub, isOnLocalDate, transformPage, transformWeekPage)


โฐ Calendar-Day Split

The worker now uses the normal Eastern calendar day for event grouping.

Clock time in New YorkWorker treats as โ€œtodayโ€Effect on panels
11:00 PM TuesdayTuesdayTuesday events are in todayEvents; Wednesday+ events are in weekEvents
12:30 AM WednesdayWednesdayWednesday events are in todayEvents; weekEvents starts on Thursday
4:01 AM WednesdayWednesdaySame split as the rest of Wednesday; there is no special 4 AM rollover

This makes panel membership match what viewers expect from the wall clock: midnight starts the new day.

Sources: worker.js (fetchFromNotion, addCalendarDays, getDateKeyInTimeZone)


๐ŸŸข Event Status Logic

Each event is evaluated every minute client-side using ISO timestamps from the API.

StatusConditionBadge
NowstartMs โ‰ค now < endMs๐ŸŸข Now Happening
Nextstarts within 90 minutesโฑ Coming Up Next
Laterstarts > 90 minutes from now๐Ÿ•’ Coming Soon
Doneend time has passedhidden
  • The Featured Event panel shows the highest-priority non-Done event (Now > Next > Later)
  • The Coming Up list filters out any Done rows before rendering
  • If an event has no usable end timestamp, the client falls back to start + 1 hour for status math
  • If all same-day events are Done, the featured panel shows No more events today

Sources: public/tv/script.js (deriveStatusFromEvent, selectFeaturedEvent, renderWeekEvents)


๐ŸŒŸ Happening at the Pub

This panel renders from todayEvents only.

  1. The client re-derives every eventโ€™s live status from startIso / endIso
  2. It picks a single featured row using priority order Now โ†’ Next โ†’ Later
  3. It renders the featured title, RC badge, and time range
  4. It then lists the remaining non-Done same-day events under Also today

Important behavior:

  • Same-day events are already sorted by start time in the API, so โ€œfirst Now/Next/Laterโ€ effectively means the earliest matching event wins
  • The panel can show one featured event plus a secondary list of other active same-day events
  • If there is no non-Done same-day event, the panel stays visible but shows the empty-state message instead

Sources: public/tv/script.js (withLiveStatuses, selectFeaturedEvent, renderFeaturedEvent)


๐Ÿ“ Future TODO

  • Consider a later redesign where the TV can switch the entire screen state on a per-event basis instead of only swapping content inside the current fixed layout
  • Likely uses: event-specific visual themes, takeover layouts for marquee events, or custom screen treatments keyed off event metadata
  • Not implemented today; the current TV always uses one shared board layout and only changes panel content/states within that frame

  • The featured panel heading is neutral: โ€œHappening at the Pubโ€
  • Later same-day events use the neutral badge text โ€œComing Soonโ€ instead of Today / Tonight
  • This avoids midnight ambiguity while still letting the eventโ€™s actual date/time carry the scheduling meaning

Sources: public/tv/script.js (renderFeaturedEvent)


๐Ÿท๏ธ RC Classification Badges

Events can carry one of two RC classification tags, shown as a pill badge before the event title.

TagBadge styleIconMeaning
rc-directSolid greenโœฆOrganized by an RC member
rc-affiliateOutlined accent (purple)โ†—Resident-led, supported by the RC
  • Badge displays the RC logo (RC-transparent-background-logo.png) alongside the icon
  • On the solid green (rc-direct) background, the logo is darkened via filter: brightness(0) for readability
  • Tag is extracted from the Notion Source Email Subject property by matching the substrings RC-Direct or RC-Affiliate (case-insensitive)

Sources: public/tv/script.js (rcBadgeHtml, extractRcTag), public/tv/styles.css (.rc-badge)


๐Ÿ“ข Announcements Panel

  • Renders a list of announcement cards from the Announcements Notion DB
  • Only rows with Include on Pub TV checked are returned by the worker
  • If the list overflows its container, an auto-scroll animation plays (scroll down โ†’ pause โ†’ scroll back up โ†’ repeat)
  • Falls back to โ€œNo announcements right nowโ€ if empty
  • The client has a special visual style for priority: 'high', but the current worker only sends announcement text, so all announcements render in the normal state today

Sources: public/tv/script.js (renderAnnouncements, startAutoScroll)


๐Ÿ“… Coming Up Panel

  • Renders from weekEvents, which the worker defines as calendar tomorrow through calendar day +7
  • In normal daytime operation, same-day events are intentionally excluded from this panel
  • Events whose computed status is Done are removed before rendering
  • Long event titles animate with a horizontal marquee scroll
  • Panel is hidden entirely if no upcoming events remain

Sources: public/tv/script.js (renderWeekEvents, startWeekTitleScroll)


๐Ÿบ Pub Hours Grid

  • 7-column grid showing Sundayโ€“Saturday pub hours for the current week
  • Todayโ€™s column has three visual states:
  • amber warning-style highlight before todayโ€™s first shift segment starts, or between multiple segments
  • green highlight while a shift segment is currently active
  • dimmed โ€œdoneโ€ style after the last segment has ended
  • Overnight segments like 9:00 PM โ€“ 1:40 AM are treated as ending on the next day when determining those states
  • Shift window math is evaluated explicitly in America/New_York, not the browserโ€™s local timezone
  • If an overnight segment is still active after midnight, the grid keeps the previous shift day highlighted until that operational window ends. Example: a Wednesday 9:00 PM โ€“ 1:40 AM shift still highlights Wednesday at 12:19 AM on Thursday; Thursday remains in the upcoming state until its own shift begins, even if the display device itself is not set to Eastern time.

Sources: public/tv/script.js (renderPubHoursWeek, getShiftStateForDate, getEffectivePubHoursDate)


Persistent footer bar showing the on-duty tender and live shift state.

FieldContent
Pub TenderFirst name(s) of on-duty tender(s)
Serving HoursOperational timeframe (e.g. 9:00 PM โ€“ 11:40 PM)
Shift StateLive note with colour/animation

Shift state transitions

StateNote textVisual
upcomingUpcoming shiftNormal
setupSetting upAmber
activeOn duty nowNormal
last-callLast callRed, pulsing
closedCleaning / closedDimmed
inactiveNo active shift right nowNormal
  • State is re-derived in the worker at request time using the shiftโ€™s full (Date/Time) and operational (Operational Timeframe) date ranges, plus the Last Call minutes property
  • The gap between the operational end and the full shift end triggers the closed state (cleaning time)

Sources: public/tv/script.js (renderPubTender), worker.js (deriveShiftNote), public/tv/styles.css (.footer-item--note)


๐Ÿ”„ Refresh Strategy

MechanismIntervalPurpose
Featured event re-evaluation1 min (client-side, no network)Keeps Now/Next/Later/Done badges accurate between API calls
Soft data refresh2 min (fetch /api/events)Pulls new events, announcements, tender data
Hard meta refresh5 minSafety net if JS hangs
Countdown displayLiveShows โ€œRefreshing in M:SSโ€ in footer

Sources: public/tv/script.js (init, startRefreshCountdown), public/tv/index.html (<meta http-equiv="refresh">)