From ce7717f26dfeaeabc59605a4b6df07996da6170a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 05:45:55 +0000 Subject: [PATCH] refactor(countdown): drop indirection left over from the row sections renderRow had a single call site and recomputed activeEntryId, which the caller had already derived to pick the scroll anchor. Inlining it brings the row back to the shape it had before the sections were introduced. The section wrapper no longer carries flex-grow. The list is sized by its content and never fills the column, so there is no free space to distribute and the growth factor resolved to nothing. Sections stay flex containers though, otherwise the row margins collapse and the rows of a section sit closer together than the sections themselves. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GjX7D56AW8cL3FXjvtnjtL --- .../features/operator/Operator.module.scss | 4 +- .../client/src/views/countdown/Countdown.scss | 6 +- .../countdown/CountdownSubscriptions.tsx | 89 +++++++++---------- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/apps/client/src/features/operator/Operator.module.scss b/apps/client/src/features/operator/Operator.module.scss index 6b410b5b2..7bb2c3782 100644 --- a/apps/client/src/features/operator/Operator.module.scss +++ b/apps/client/src/features/operator/Operator.module.scss @@ -18,11 +18,11 @@ padding-bottom: 95vh; } -/* a group and its events, bounds the sticky group header to its own section */ +/* a group and its events, bounds the sticky group header to its own section. + repeats the gap of the surrounding list, which no longer reaches the rows of a section */ .groupSection { display: flex; flex-direction: column; - /* the rows used to be direct children of the list, keep the gap they had */ gap: 2px; } diff --git a/apps/client/src/views/countdown/Countdown.scss b/apps/client/src/views/countdown/Countdown.scss index f72aaab84..c411f85db 100644 --- a/apps/client/src/views/countdown/Countdown.scss +++ b/apps/client/src/views/countdown/Countdown.scss @@ -103,12 +103,12 @@ $item-height: 3.5rem; padding-bottom: max(8rem, calc(5rem + env(safe-area-inset-bottom))); } - /* a subscribed group and its subscribed events, bounds the sticky header to its own section */ + /* a subscribed group and its subscribed events, bounds the sticky header to its own section. + flex rather than block so that the row margins do not collapse, keeping the spacing between + rows of a section equal to the spacing between sections */ .sub-section { display: flex; flex-direction: column; - /* flex-grow is set inline from the row count, so that the rows keep the share they had in the list */ - flex: 1 1 0; } /* scoped to the section, the select view shows the same cards in a flat list */ diff --git a/apps/client/src/views/countdown/CountdownSubscriptions.tsx b/apps/client/src/views/countdown/CountdownSubscriptions.tsx index 895621297..5ec64ebf1 100644 --- a/apps/client/src/views/countdown/CountdownSubscriptions.tsx +++ b/apps/client/src/views/countdown/CountdownSubscriptions.tsx @@ -1,6 +1,6 @@ import { MaybeNumber, OntimeEvent } from 'ontime-types'; import { dayInMs } from 'ontime-utils'; -import { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { IoPencil } from 'react-icons/io5'; import Button from '../../common/components/buttons/Button'; @@ -112,44 +112,6 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode throttledHandleScroll(); }; - const renderRow = (event: CountdownTarget, rowRef: RefObject | undefined, isLive: boolean) => { - // while a group is live, surface the running event's title as the secondary line - const liveTitle = event.isGroup && event.liveEntry ? event.liveEntry.title : undefined; - const secondaryData = liveTitle ?? getPropertyValue(event, secondarySource); - const isGroupedEvent = !event.isGroup && Boolean(event.parent); - const activeEntryId = event.isGroup ? (event.liveEntry?.id ?? event.targetId) : event.id; - const isArmed = !isLive && activeEntryId === selectedEventId; - const countdownEvent = extendEventData(event, currentDay, actualStart, plannedStart, offset, mode, reportData); - const displayTitle = getPropertyValue(event, mainSource ?? 'title'); - - return ( -
-
- - -
- {event.isGroup && Group} - {displayTitle} -
- {secondaryData &&
{secondaryData}
} -
- ); - }; - return (
{sections.map((section) => { @@ -158,21 +120,56 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode const anchorId = section.events.find((event) => getIsLive(event.id, selectedEventId, playback))?.id ?? null; return ( -
+
{rows.map((event) => { + // while a group is live, surface the running event's title as the secondary line + const liveTitle = event.isGroup && event.liveEntry ? event.liveEntry.title : undefined; + const secondaryData = liveTitle ?? getPropertyValue(event, secondarySource); + const isGroupedEvent = !event.isGroup && Boolean(event.parent); const activeEntryId = event.isGroup ? (event.liveEntry?.id ?? event.targetId) : event.id; // a subscribed group is live when any of its children is the selected/running event const isLive = activeEntryId ? getIsLive(activeEntryId, selectedEventId, playback) : false; + const isArmed = !isLive && activeEntryId === selectedEventId; // only ever hand the ref to a single row, sharing it would null it out on the next commit const isAnchor = isLive && (anchorId === null || event.id === anchorId); const rowRef = isAnchor ? selectedRef : event.isGroup && anchorId ? stickyHeaderRef : undefined; + const countdownEvent = extendEventData( + event, + currentDay, + actualStart, + plannedStart, + offset, + mode, + reportData, + ); + const displayTitle = getPropertyValue(event, mainSource ?? 'title'); - return renderRow(event, rowRef, isLive); + return ( +
+
+ + +
+ {event.isGroup && Group} + {displayTitle} +
+ {secondaryData &&
{secondaryData}
} +
+ ); })}
);