Skip to content

feat: add t-timers info to LSG#1708

Merged
rjmunro merged 8 commits intoSofie-Automation:mainfrom
bbc:rjmunro/t-timers-lsg
Apr 17, 2026
Merged

feat: add t-timers info to LSG#1708
rjmunro merged 8 commits intoSofie-Automation:mainfrom
bbc:rjmunro/t-timers-lsg

Conversation

@rjmunro
Copy link
Copy Markdown
Contributor

@rjmunro rjmunro commented Apr 1, 2026

About the Contributor

This PR is posted on behalf of the BBC

Type of Contribution

This is a: feature

Current Behavior

Currently, t-timers state is not available through LSG

New Behavior

This PR adds t-timers state to the LSG in the following form:

Testing

  • I have added one or more unit tests for this PR
  • I have updated the relevant unit tests
  • No unit test changes are needed for this PR

Affected areas

This PR affects the LSG outputs

Time Frame

Not urgent, but we would like to get this merged into the in-development release.

Other Information

Replaces bbc#74 - merges upstream rather than to BBC fork.

Status

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Walkthrough

Adds T-Timers: new OpenAPI schemas (indices, modes, states, status), required tTimers (3-item array) on ActivePlaylistEvent, transformers in ActivePlaylistTopic to populate a 3-entry timers array, sample-client UI rendering and periodic updates, and test updates asserting tTimers presence.

Changes

Cohort / File(s) Summary
T-Timer schema & examples
packages/live-status-gateway-api/api/components/tTimers/tTimerIndex.yaml, .../tTimerStatus.yaml, tTimerStatus/tTimerStatus.yaml, tTimerStatus/tTimerStatus-array-example.yaml, tTimerStatus/tTimerStatus-configured-example.yaml, tTimerStatus/tTimerStatus-unconfigured-example.yaml, timerMode.yaml, timerMode/countdown/timerModeCountdown.yaml, timerMode/countdown/timerModeCountdown-example.yaml, timerMode/freeRun/timerModeFreeRun.yaml, timerMode/freeRun/timerModeFreeRun-example.yaml, timerMode/timeOfDay/timerModeTimeOfDay.yaml, timerMode/timeOfDay/timerModeTimeOfDay-example.yaml, timerState.yaml, timerState/running/timerStateRunning.yaml, timerState/running/timerStateRunning-example.yaml, timerState/paused/timerStatePaused.yaml, timerState/paused/timerStatePaused-example.yaml
Added full T-Timers domain: index enum (1–3), timer status schema, three mode schemas (countdown, freeRun, timeOfDay), two runtime states (running, paused), and multiple examples (configured/unconfigured, arrays).
ActivePlaylistEvent schema & example
packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent.yaml, .../activePlaylistEvent-example.yaml
Extended ActivePlaylistEvent to require tTimers property: array of exactly 3 tTimerStatus items; updated example to include tTimers.
Topic implementation & tests
packages/live-status-gateway/src/topics/activePlaylistTopic.ts, packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts
Added transformTTimers / transformTTimer logic to emit a fixed-length 3-item tTimers array (default unconfigured timers when absent); updated tests to expect tTimers and added a configured-timer test case.
Sample client UI & logic
packages/live-status-gateway/sample-client/index.html, packages/live-status-gateway/sample-client/script.js
Added #t-timers placeholder and client-side rendering: handleTTimers, updateTTimers, formatting helpers, and periodic updates to compute current and projected timer values from zeroTime/pauseTime.
Build configs
packages/live-status-gateway-api/tsconfig.build.json, packages/tsconfig.build.json
Enabled declaration generation for live-status-gateway-api and added it to workspace build references.

Sequence Diagram

sequenceDiagram
    participant Corelib as Corelib (Data Model)
    participant Topic as ActivePlaylistTopic (Transformer)
    participant Gateway as WebSocket (Event Dispatch)
    participant Client as Sample Client (Receiver / Renderer)

    Corelib->>Topic: emit playlist with RundownTTimer[]
    activate Topic
    Topic->>Topic: transformTTimers() → TTimerStatus[3]
    Topic->>Gateway: send activePlaylist event { ..., tTimers }
    deactivate Topic

    Gateway->>Client: WebSocket message (activePlaylist)
    activate Client
    Client->>Client: parse activePlaylist.tTimers
    Client->>Client: render timer DOM placeholders
    loop periodic (e.g., 100ms)
        Client->>Client: updateTTimers() — compute values from zeroTime/pauseTime/projected
        Client->>Client: update DOM timer values & projected indicators
    end
    deactivate Client
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Contribution, ✨ enhancement

Suggested reviewers

  • Julusian
  • PeterC89
  • jstarpl
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add t-timers info to LSG' clearly and concisely summarizes the main change: adding T-timers information to the Live Status Gateway, which aligns with all file changes in the PR.
Description check ✅ Passed The PR description directly addresses the changeset by explaining the current behavior (t-timers not available), the new behavior (adding t-timers state), and confirming testing and documentation updates align with the changes made.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@jstarpl jstarpl added the Contribution from BBC Contributions sponsored by BBC (bbc.co.uk) label Apr 2, 2026
@PeterC89
Copy link
Copy Markdown
Contributor

PeterC89 commented Apr 9, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused.yaml (1)

11-23: Prefer integer types for ms/timestamp fields.

Line 12 (duration) and Line 22 (pauseTime) currently allow fractional numbers; integer typing would better match millisecond semantics.

🛠️ Suggested schema tweak
       duration:
-        type: number
+        type: integer
         description: >-
           Frozen duration value in milliseconds.
           For countdown timers, this is remaining time.
           For free-run timers, this is elapsed time.
...
         oneOf:
-          - type: number
+          - type: integer
           - type: 'null'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused.yaml`
around lines 11 - 23, The duration and pauseTime schema fields currently use
type:number which allows fractional values; update duration's type to integer
and change the pauseTime oneOf entry from type:number to type:integer so both
fields enforce millisecond/timestamp integer semantics (leave descriptions and
the null option for pauseTime unchanged); locate the duration and pauseTime
entries in timerStatePaused.yaml to make these edits.
packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown.yaml (1)

10-12: Tighten duration to integer milliseconds.

On Line 11, type: number permits fractional values. For millisecond durations, integer (and non-negative bound) is usually safer.

🛠️ Suggested schema tweak
       duration:
-        type: number
+        type: integer
+        minimum: 0
         description: The original countdown duration in milliseconds (used for reset)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown.yaml`
around lines 10 - 12, The schema's duration field currently allows fractional
numbers; update the timerModeCountdown schema's duration property to use type:
integer and add a minimum: 0 constraint so it only accepts non-negative
millisecond integers (i.e., change the duration definition in
timerModeCountdown.yaml to type: integer and include minimum: 0).
packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts (2)

66-94: Consider adding a configured T-Timer expectation test.

These updates assert the default/unconfigured case, but a focused test for a configured timer (mode/state/projected/anchorPartId) would better protect the new mapping path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts`
around lines 66 - 94, Add a focused test in activePlaylist.spec.ts that covers a
configured T-Timer mapping path: create a tTimers entry with configured: true
and non-null values for mode, state, projected, and anchorPartId (e.g., for
index 1), then assert that the code which maps/returns tTimers (the logic under
test referenced by tTimers in the spec) preserves those fields and produces the
expected mapped output; use the existing test structure to add an expectation
verifying mode/state/projected/anchorPartId are present and correct for the
configured timer.

66-94: Extract duplicated default tTimers fixture.

Lines 66-94, 196-224, and 331-359 repeat the same array literal; pulling this into a shared constant/helper will make future schema updates safer.

♻️ Suggested refactor
+const DEFAULT_UNCONFIGURED_T_TIMERS: ActivePlaylistEvent['tTimers'] = [
+	{ index: 1, label: '', configured: false, mode: null, state: null, projected: null, anchorPartId: null },
+	{ index: 2, label: '', configured: false, mode: null, state: null, projected: null, anchorPartId: null },
+	{ index: 3, label: '', configured: false, mode: null, state: null, projected: null, anchorPartId: null },
+]
...
-			tTimers: [
-				{ ... },
-				{ ... },
-				{ ... },
-			],
+			tTimers: DEFAULT_UNCONFIGURED_T_TIMERS,

Also applies to: 196-224, 331-359

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts`
around lines 66 - 94, The test file repeats the same tTimers array literal in
multiple places; extract it into a single shared constant (e.g.,
DEFAULT_T_TIMERS) declared once at the top of the spec and replace the three
inline literals with references to that constant, ensuring tests that mutate the
array clone it (e.g., structuredClone or JSON.parse(JSON.stringify(...))) before
mutating; update usages of the inline tTimers to use DEFAULT_T_TIMERS so schema
changes only need one edit and tests remain independent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-configured-example.yaml`:
- Around line 4-7: The example uses literal $ref keys inside the example object
(fields mode and state) which will not be dereferenced by JSON Schema/AsyncAPI
tooling; replace those $ref entries by inlining the actual example objects from
../timerMode/countdown/timerModeCountdown-example.yaml and
../timerState/running/timerStateRunning-example.yaml directly into the mode and
state properties in tTimerStatus-configured-example.yaml, or implement a
preprocessing step to merge those example files into the example payload before
tooling runs so mode and state contain real example objects rather than "{
\"$ref\": ... }".

---

Nitpick comments:
In
`@packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown.yaml`:
- Around line 10-12: The schema's duration field currently allows fractional
numbers; update the timerModeCountdown schema's duration property to use type:
integer and add a minimum: 0 constraint so it only accepts non-negative
millisecond integers (i.e., change the duration definition in
timerModeCountdown.yaml to type: integer and include minimum: 0).

In
`@packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused.yaml`:
- Around line 11-23: The duration and pauseTime schema fields currently use
type:number which allows fractional values; update duration's type to integer
and change the pauseTime oneOf entry from type:number to type:integer so both
fields enforce millisecond/timestamp integer semantics (leave descriptions and
the null option for pauseTime unchanged); locate the duration and pauseTime
entries in timerStatePaused.yaml to make these edits.

In `@packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts`:
- Around line 66-94: Add a focused test in activePlaylist.spec.ts that covers a
configured T-Timer mapping path: create a tTimers entry with configured: true
and non-null values for mode, state, projected, and anchorPartId (e.g., for
index 1), then assert that the code which maps/returns tTimers (the logic under
test referenced by tTimers in the spec) preserves those fields and produces the
expected mapped output; use the existing test structure to add an expectation
verifying mode/state/projected/anchorPartId are present and correct for the
configured timer.
- Around line 66-94: The test file repeats the same tTimers array literal in
multiple places; extract it into a single shared constant (e.g.,
DEFAULT_T_TIMERS) declared once at the top of the spec and replace the three
inline literals with references to that constant, ensuring tests that mutate the
array clone it (e.g., structuredClone or JSON.parse(JSON.stringify(...))) before
mutating; update usages of the inline tTimers to use DEFAULT_T_TIMERS so schema
changes only need one edit and tests remain independent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d969bd02-76fd-4868-88f1-74ee6137d31e

📥 Commits

Reviewing files that changed from the base of the PR and between 7ef4508 and 39d94f5.

⛔ Files ignored due to path filters (2)
  • packages/live-status-gateway-api/src/generated/asyncapi.yaml is excluded by !**/generated/**
  • packages/live-status-gateway-api/src/generated/schema.ts is excluded by !**/generated/**
📒 Files selected for processing (24)
  • packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent-example.yaml
  • packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerIndex.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-array-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-configured-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-unconfigured-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/freeRun/timerModeFreeRun-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/freeRun/timerModeFreeRun.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/timeOfDay/timerModeTimeOfDay-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/timeOfDay/timerModeTimeOfDay.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/running/timerStateRunning-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/running/timerStateRunning.yaml
  • packages/live-status-gateway/sample-client/index.html
  • packages/live-status-gateway/sample-client/script.js
  • packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts
  • packages/live-status-gateway/src/topics/activePlaylistTopic.ts

@rjmunro rjmunro mentioned this pull request Apr 15, 2026
7 tasks
@rjmunro rjmunro force-pushed the rjmunro/t-timers-lsg branch 4 times, most recently from e3aca49 to d3c11c8 Compare April 16, 2026 14:59
@justandras justandras self-requested a review April 17, 2026 09:31
rjmunro and others added 8 commits April 17, 2026 13:38
This adds basic T-Timer support to the Live Status Gateway API by
exposing the underlying data model structure directly, rather than
denormalizing/reshaping it.

Structure:
- TimerMode: Configuration (countdown/freeRun/timeOfDay)
- TimerState: Runtime data (paused/running, zeroTime/duration)
- TTimerStatus: Combines mode, state, label, configured flag

This keeps the transformation layer thin - the API just passes through
the well-designed internal structures. The types match the corelib
TimerState and RundownTTimerMode types exactly.

Benefits over previous denormalized approach:
- 90% less transformation code
- Single source of truth for TimerState structure
- Easier to add timeOfDay mode support later
- Self-documenting (reuses existing inline docs)

Schema generation: Added tTimers example to activePlaylistEvent-example.yaml
to fix AsyncAPI parser validation. The parser requires all required fields
to be present in examples.

Tests: Updated activePlaylist.spec.ts to include tTimers in all cases
- Add pauseTime field to TimerState (both running and paused states)
  - Running: timestamp for automatic pause when part ends
  - Paused: typically null when already paused
- Add projected field to TTimerStatus (TimerState | null)
  - Enables calculation of over/under diff relative to anchor
  - Running state means progressing towards anchor
  - Paused state means pushing/delaying anchor
- Add anchorPartId field to TTimerStatus (string | null)
  - Target Part ID that timer is counting towards
- Update example with realistic projected/pauseTime/anchorPartId values
- Fix imports: use specific timer mode/state types and define local unions
- Update all test cases to include new fields (projected, anchorPartId)
- All tests passing (17/17)
Break down T-Timer schemas and examples into separate files for reusability:

Timer Modes (with examples):
- timerMode/countdown/ - Countdown timer with duration
- timerMode/freeRun/ - Free-running timer
- timerMode/timeOfDay/ - Time-of-day target timer

Timer States (with examples):
- timerState/running/ - Running state with zeroTime and pauseTime
- timerState/paused/ - Paused state with frozen duration

T-Timer Status (with examples):
- tTimerStatus-configured-example.yaml - Configured timer
- tTimerStatus-unconfigured-example.yaml - Empty timer slot
- tTimerStatus-array-example.yaml - Array of 3 timers for reuse

This follows the pattern used in other components (e.g., notifications/target/)
and allows individual examples and schemas to be referenced independently.
- Add formatTimestampToTimeOfDay() to display timestamps in HH:MM:SS format
- Show timer state context: (paused), (pauseTime: HH:MM:SS), or (zeroTime: HH:MM:SS)
- Factor in pauseTime when calculating timer display values to freeze countdown correctly
- Apply same logic to both current and projected timer states
@justandras justandras force-pushed the rjmunro/t-timers-lsg branch from c7cd646 to 37d2d77 Compare April 17, 2026 11:44
Copy link
Copy Markdown
Member

@justandras justandras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All changes have been previously reviewed and approved

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/live-status-gateway/src/topics/activePlaylistTopic.ts (1)

306-316: Type safety: make timer transformation more robust to future schema changes.

The as casts (mode as TimerMode | null, state as TimerState | null, projected: projectedState as TimerState) currently work because corelib and API timer types are structurally identical. However, these unchecked casts bypass the compiler's structural verification. If corelib ever adds a new mode variant (e.g., with different field casing), renames a discriminant, or changes field types, the transformation will silently serialize incorrect payloads to LSG clients without a compile-time signal.

Consider adding a small discriminant switch (on mode.type and state.paused) with assertNever in the default case to catch schema drift early. Alternatively, follow the pattern of transformQuickLoopMarkerStatus with an explicit mapper. This adds minimal runtime overhead but provides early warning of breaking changes.

The ternary projectedState ? (projectedState as TimerState) : null is appropriate as-is; it clearly expresses the intent to return null only when projectedState is absent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/live-status-gateway/src/topics/activePlaylistTopic.ts` around lines
306 - 316, The transformTTimer function currently uses unchecked `as` casts for
mode/state which can hide schema drift; replace those casts in transformTTimer
by mapping the RundownTTimer.mode and .state through explicit discriminant-based
conversions (e.g., switch on mode.type and on state.paused or another
discriminant) similar to transformQuickLoopMarkerStatus, returning the
corresponding TimerMode/TimerState shapes and using an assertNever (or
equivalent) in the default branch to fail loudly on unknown variants; keep the
projectedState ternary (projectedState ? projectedState as TimerState : null)
as-is per the review.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/live-status-gateway/src/topics/activePlaylistTopic.ts`:
- Around line 269-304: The transformTTimers function's parameter tTimers is
declared as RundownTTimer[] but the data model guarantees a fixed 3-tuple;
change the parameter type to [RundownTTimer, RundownTTimer, RundownTTimer] |
null | undefined so the signature reflects the invariant, keeping the body
(positional indexing and the return of three this.transformTTimer calls)
unchanged; update the import or type reference if necessary to use the
RundownTTimer tuple form and ensure TTimerStatus/TTimerIndex usages remain
compatible.

---

Nitpick comments:
In `@packages/live-status-gateway/src/topics/activePlaylistTopic.ts`:
- Around line 306-316: The transformTTimer function currently uses unchecked
`as` casts for mode/state which can hide schema drift; replace those casts in
transformTTimer by mapping the RundownTTimer.mode and .state through explicit
discriminant-based conversions (e.g., switch on mode.type and on state.paused or
another discriminant) similar to transformQuickLoopMarkerStatus, returning the
corresponding TimerMode/TimerState shapes and using an assertNever (or
equivalent) in the default branch to fail loudly on unknown variants; keep the
projectedState ternary (projectedState ? projectedState as TimerState : null)
as-is per the review.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6e85dc1a-3a9b-4195-b023-70e9e60ded95

📥 Commits

Reviewing files that changed from the base of the PR and between 39d94f5 and 37d2d77.

⛔ Files ignored due to path filters (2)
  • packages/live-status-gateway-api/src/generated/asyncapi.yaml is excluded by !**/generated/**
  • packages/live-status-gateway-api/src/generated/schema.ts is excluded by !**/generated/**
📒 Files selected for processing (26)
  • packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent-example.yaml
  • packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerIndex.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-array-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-configured-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-unconfigured-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/freeRun/timerModeFreeRun-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/freeRun/timerModeFreeRun.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/timeOfDay/timerModeTimeOfDay-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/timeOfDay/timerModeTimeOfDay.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/running/timerStateRunning-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/running/timerStateRunning.yaml
  • packages/live-status-gateway-api/tsconfig.build.json
  • packages/live-status-gateway/sample-client/index.html
  • packages/live-status-gateway/sample-client/script.js
  • packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts
  • packages/live-status-gateway/src/topics/activePlaylistTopic.ts
  • packages/tsconfig.build.json
✅ Files skipped from review due to trivial changes (20)
  • packages/live-status-gateway-api/api/components/tTimers/timerState/running/timerStateRunning-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/timeOfDay/timerModeTimeOfDay-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/freeRun/timerModeFreeRun-example.yaml
  • packages/tsconfig.build.json
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-unconfigured-example.yaml
  • packages/live-status-gateway-api/tsconfig.build.json
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerIndex.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-configured-example.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState/running/timerStateRunning.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerState.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus-array-example.yaml
  • packages/live-status-gateway/sample-client/index.html
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/freeRun/timerModeFreeRun.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/timeOfDay/timerModeTimeOfDay.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus/tTimerStatus.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode/countdown/timerModeCountdown.yaml
  • packages/live-status-gateway-api/api/components/tTimers/tTimerStatus.yaml
  • packages/live-status-gateway-api/api/components/tTimers/timerMode.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/live-status-gateway-api/api/components/tTimers/timerState/paused/timerStatePaused-example.yaml
  • packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent-example.yaml
  • packages/live-status-gateway/sample-client/script.js
  • packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent.yaml
  • packages/live-status-gateway/src/topics/tests/activePlaylist.spec.ts

Comment on lines +269 to +304
private transformTTimers(tTimers: RundownTTimer[] | null | undefined): [TTimerStatus, TTimerStatus, TTimerStatus] {
// Always return exactly 3 timers
if (!tTimers || tTimers.length === 0) {
return [
{
index: 1 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
{
index: 2 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
{
index: 3 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
]
}

return [this.transformTTimer(tTimers[0]), this.transformTTimer(tTimers[1]), this.transformTTimer(tTimers[2])]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all sites that write/initialize/mutate tTimers on RundownPlaylist to verify invariants.
rg -nP --type=ts -C3 '\btTimers\b' -g '!**/__tests__/**' -g '!**/*.spec.ts'

Repository: Sofie-Automation/sofie-core

Length of output: 22639


Fix misleading type annotation for tTimers parameter; positional indexing is correct given data model invariant.

The data model (packages/corelib/src/dataModel/RundownPlaylist/RundownPlaylist.ts:191) defines tTimers as a fixed tuple of exactly 3 elements: [RundownTTimer, RundownTTimer, RundownTTimer]. All initialization and mutation sites enforce this invariant (see job-worker, migration, and mock fixtures). The parameter type on line 269 should therefore be [RundownTTimer, RundownTTimer, RundownTTimer] | null | undefined instead of RundownTTimer[], which is misleading and masks the fixed-size guarantee. The positional indexing [0], [1], [2] is correct because the data is always ordered by index 1, 2, 3 in that respective order.

Change the type annotation from RundownTTimer[] to the precise tuple type to improve type safety and prevent accidental misuse:

Type annotation fix
-	private transformTTimers(tTimers: RundownTTimer[] | null | undefined): [TTimerStatus, TTimerStatus, TTimerStatus] {
+	private transformTTimers(tTimers: [RundownTTimer, RundownTTimer, RundownTTimer] | null | undefined): [TTimerStatus, TTimerStatus, TTimerStatus] {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private transformTTimers(tTimers: RundownTTimer[] | null | undefined): [TTimerStatus, TTimerStatus, TTimerStatus] {
// Always return exactly 3 timers
if (!tTimers || tTimers.length === 0) {
return [
{
index: 1 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
{
index: 2 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
{
index: 3 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
]
}
return [this.transformTTimer(tTimers[0]), this.transformTTimer(tTimers[1]), this.transformTTimer(tTimers[2])]
}
private transformTTimers(tTimers: [RundownTTimer, RundownTTimer, RundownTTimer] | null | undefined): [TTimerStatus, TTimerStatus, TTimerStatus] {
// Always return exactly 3 timers
if (!tTimers || tTimers.length === 0) {
return [
{
index: 1 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
{
index: 2 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
{
index: 3 as TTimerIndex,
label: '',
configured: false,
mode: null,
state: null,
projected: null,
anchorPartId: null,
},
]
}
return [this.transformTTimer(tTimers[0]), this.transformTTimer(tTimers[1]), this.transformTTimer(tTimers[2])]
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/live-status-gateway/src/topics/activePlaylistTopic.ts` around lines
269 - 304, The transformTTimers function's parameter tTimers is declared as
RundownTTimer[] but the data model guarantees a fixed 3-tuple; change the
parameter type to [RundownTTimer, RundownTTimer, RundownTTimer] | null |
undefined so the signature reflects the invariant, keeping the body (positional
indexing and the return of three this.transformTTimer calls) unchanged; update
the import or type reference if necessary to use the RundownTTimer tuple form
and ensure TTimerStatus/TTimerIndex usages remain compatible.

@rjmunro rjmunro merged commit 255d0c6 into Sofie-Automation:main Apr 17, 2026
24 checks passed
@rjmunro rjmunro deleted the rjmunro/t-timers-lsg branch April 17, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Contribution from BBC Contributions sponsored by BBC (bbc.co.uk)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants