๐งญ 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/eventsevery 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 TVmust be checkedLocationmust 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
todayISOinAmerica/New_York - The event day starts at 12:00 AM Eastern
- The worker queries a broad Notion window from
todayISOthrough< todayISO + 2 days - It then narrows that result set to rows whose
Event Startfalls ontodayISOin 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 >= tomorrowISOand< 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 York | Worker treats as โtodayโ | Effect on panels |
|---|---|---|
| 11:00 PM Tuesday | Tuesday | Tuesday events are in todayEvents; Wednesday+ events are in weekEvents |
| 12:30 AM Wednesday | Wednesday | Wednesday events are in todayEvents; weekEvents starts on Thursday |
| 4:01 AM Wednesday | Wednesday | Same 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.
| Status | Condition | Badge |
|---|---|---|
Now | startMs โค now < endMs | ๐ข Now Happening |
Next | starts within 90 minutes | โฑ Coming Up Next |
Later | starts > 90 minutes from now | ๐ Coming Soon |
Done | end time has passed | hidden |
- The Featured Event panel shows the highest-priority non-
Doneevent (Now>Next>Later) - The Coming Up list filters out any
Donerows before rendering - If an event has no usable end timestamp, the client falls back to
start + 1 hourfor status math - If all same-day events are
Done, the featured panel showsNo more events today
Sources: public/tv/script.js (deriveStatusFromEvent, selectFeaturedEvent, renderWeekEvents)
๐ Happening at the Pub
This panel renders from todayEvents only.
- The client re-derives every eventโs live status from
startIso/endIso - It picks a single featured row using priority order
NowโNextโLater - It renders the featured title, RC badge, and time range
- It then lists the remaining non-
Donesame-day events underAlso 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-
Donesame-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
๐ท๏ธ Featured Badges
- 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.
| Tag | Badge style | Icon | Meaning |
|---|---|---|---|
rc-direct | Solid green | โฆ | Organized by an RC member |
rc-affiliate | Outlined 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 viafilter: brightness(0)for readability - Tag is extracted from the Notion Source Email Subject property by matching the substrings
RC-DirectorRC-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 TVchecked 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
Doneare 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 AMare 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 AMshift still highlights Wednesday at12:19 AMon 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)
๐ค Pub Tender Footer
Persistent footer bar showing the on-duty tender and live shift state.
| Field | Content |
|---|---|
| Pub Tender | First name(s) of on-duty tender(s) |
| Serving Hours | Operational timeframe (e.g. 9:00 PM โ 11:40 PM) |
| Shift State | Live note with colour/animation |
Shift state transitions
| State | Note text | Visual |
|---|---|---|
upcoming | Upcoming shift | Normal |
setup | Setting up | Amber |
active | On duty now | Normal |
last-call | Last call | Red, pulsing |
closed | Cleaning / closed | Dimmed |
inactive | No active shift right now | Normal |
- State is re-derived in the worker at request time using the shiftโs full (
Date/Time) and operational (Operational Timeframe) date ranges, plus theLast Callminutes property - The gap between the operational end and the full shift end triggers the
closedstate (cleaning time)
Sources: public/tv/script.js (renderPubTender), worker.js (deriveShiftNote), public/tv/styles.css (.footer-item--note)
๐ Refresh Strategy
| Mechanism | Interval | Purpose |
|---|---|---|
| Featured event re-evaluation | 1 min (client-side, no network) | Keeps Now/Next/Later/Done badges accurate between API calls |
| Soft data refresh | 2 min (fetch /api/events) | Pulls new events, announcements, tender data |
| Hard meta refresh | 5 min | Safety net if JS hangs |
| Countdown display | Live | Shows โRefreshing in M:SSโ in footer |
Sources: public/tv/script.js (init, startRefreshCountdown), public/tv/index.html (<meta http-equiv="refresh">)