Skip to content

feat(LSG): new resolvedPlaylistTopic#1718

Open
justandras wants to merge 11 commits intoSofie-Automation:mainfrom
bbc:justandras/lsg-full-status
Open

feat(LSG): new resolvedPlaylistTopic#1718
justandras wants to merge 11 commits intoSofie-Automation:mainfrom
bbc:justandras/lsg-full-status

Conversation

@justandras
Copy link
Copy Markdown
Member

About the Contributor

This pull request is posted on behalf of the BBC and CBC.

Type of Contribution

This is a Feature

Current Behavior

The Live Status Gateway primarily exposes:

  • activePlaylist: current/next context and playlist-level timing state for the active playlist
  • activePieces: only currently active (on-air) pieces

There was no single subscription that provided a resolved, playlist-scoped hierarchy of all rundowns → segments → parts → pieces, and some part/piece validity metadata was not exposed in the resolved structures.

New Behavior

Adds a new websocket subscription/topic, resolvedPlaylist, that publishes the fully resolved active playlist aligned with the webui.

Key changes include:

  • resolvedPlaylist topic: includes playlist-level fields and a full resolved structure: rundowns → segments → parts → pieces, with publicData surfaced through the hierarchy.
  • Expose additional part validity/timing flags:
    • ResolvedPart.floated
    • ResolvedPart.untimed
    • ResolvedPart.invalidReason
  • Piece validity:
    • ResolvedPiece.invalid

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

  • Live Status Gateway websocket server topics and collections
  • Live Status Gateway API schemas and generated output
  • Schema/type generation tooling

Time Frame

This Feature is critical for us, please review and merge it as soon as possible.

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) has been added / updated.

@justandras justandras requested review from Saftret and rjmunro April 17, 2026 12:09
@justandras justandras self-assigned this Apr 17, 2026
@justandras justandras added Contribution from BBC Contributions sponsored by BBC (bbc.co.uk) Contribution from CBC/Radio-Canada Contributions sponsored by CBC/Radio-Canada (cbc.radio-canada.ca) labels Apr 17, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

Warning

Rate limit exceeded

@justandras has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 9 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 51 minutes and 9 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ac6f96f-874b-4362-9bc1-eb2fadf81052

📥 Commits

Reviewing files that changed from the base of the PR and between e8db066 and 17be85e.

📒 Files selected for processing (1)
  • packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts

Walkthrough

Added a new AsyncAPI websocket channel resolvedPlaylist plus full schema, examples, conversion logic, collection handlers, topic implementation, and tests to produce and publish aggregated resolved-playlist events (including rundowns, segments, parts, pieces, timing, tTimers and quickLoop) to subscribers.

Changes

Cohort / File(s) Summary
AsyncAPI channel & message
packages/live-status-gateway-api/api/asyncapi.yaml, packages/live-status-gateway-api/api/topics/resolvedPlaylist/resolvedPlaylistTopic.yaml, packages/live-status-gateway-api/api/components/resolvedPlaylist/messages/resolvedPlaylistMessage.yaml
Added resolvedPlaylist websocket channel, topic definition with receiveResolvedPlaylist subscribe op and message referencing the resolvedPlaylist event payload.
Resolved playlist schemas & examples
packages/live-status-gateway-api/api/components/resolvedPlaylist/resolvedPlaylistEvent/*, .../resolvedPlaylist/resolvedPlaylistEvent-example.yaml, .../resolvedPlaylist/messages/*
New comprehensive resolvedPlaylistEvent schema and examples (event, message, payload structure).
Entities: rundown / segment / part / piece schemas & examples
packages/live-status-gateway-api/api/components/rundown/resolvedRundown/*, .../segment/resolvedSegment/*, .../part/resolvedPart/*, .../piece/resolvedPiece/*, plus many example files
Added resolved-rundown, resolved-segment, resolved-part, resolved-piece schemas and examples used by the resolvedPlaylist payload.
Timing & timers schemas/examples
packages/live-status-gateway-api/api/components/timing/resolved*/**, packages/live-status-gateway-api/api/components/tTimers/*
Added resolved playlist/segment/part/piece timing schemas and timerMode/timerState examples; tTimers representation added.
Layer schemas
packages/live-status-gateway-api/api/components/layers/sourceLayer/*, packages/live-status-gateway-api/api/components/layers/outputLayer/*
Added sourceLayer and outputLayer schemas and examples referenced by segments/pieces.
Subscription enum & playlistStatus tweak
packages/live-status-gateway-api/api/components/subscriptions/subscriptionName.yaml, packages/live-status-gateway-api/api/components/playlist/playlistStatus/playlistStatus.yaml
Added resolvedPlaylist to SubscriptionName enum; removed additionalProperties: false from playlistStatus.
Collection handlers (server-side)
packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts, packages/live-status-gateway/src/collections/pieceInstancesInPlaylistHandler.ts, packages/live-status-gateway/src/collections/piecesInPlaylistHandler.ts
New PublicationCollection handlers that maintain and publish playlist-scoped part/piece and instance collections, subscribing to playlist changes and managing server-side observers.
Topic implementation & server integration
packages/live-status-gateway/src/topics/resolvedPlaylistTopic.ts, packages/live-status-gateway/src/liveStatusServer.ts, packages/live-status-gateway/src/topics/__tests__/utils.ts
Added ResolvedPlaylistTopic (throttled aggregator), wired new handlers into LiveStatusServer and test utils.
Conversion context & helpers
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts, .../events/toResolvedPlaylistStatus.ts, .../rundowns/*, .../segments/*, .../parts/*, .../pieces/*
New conversion context builder and per-entity converters to map core DB models into resolvedPlaylist API structures (includes lookups, ordering, instance finding, and mapping of timing/invalidReasons/abSessions).
ActivePlaylist helpers
packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/quickLoop/toQuickLoopStatus.ts, .../timers/toTTimers.ts, .../timing/*, .../notification/toNotificationStatus.ts
Added helpers for quickLoop conversion, tTimers normalization (3-slot tuple), playlist timing translation, and exported notification severity mapper.
Serialization helpers & tests
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/segments/serializeResolvedSegment.ts, packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/*
Added serialization utilities to avoid cycles and a comprehensive test suite covering conversion context and converters.
Script fix
packages/live-status-gateway-api/scripts/generate-schema-types.mjs
Adjusted error logging to serialize filteredDiagnostics correctly.

Sequence Diagram(s)

sequenceDiagram
    participant Client as WebSocket Client
    participant Topic as ResolvedPlaylistTopic
    participant Handlers as Collection Handlers
    participant Context as ConversionContext
    participant Converters as Conversion Helpers

    Handlers->>Topic: push updates (playlist, rundowns, segments, parts, pieces, timers)
    Topic->>Topic: cache updates & throttle
    Topic->>Context: createResolvedPlaylistConversionContext(with cached state)
    Context-->>Topic: normalized lookups (rundownsById, segmentsById, partsById, instances)
    Topic->>Converters: toResolvedPlaylistStatus(context)
    Converters->>Converters: toResolvedRundownStatus → toResolvedSegmentStatus → toResolvedPartStatus → toResolvedPieceStatus
    Converters-->>Topic: ResolvedPlaylistEvent
    Topic->>Client: broadcast resolvedPlaylist message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

Contribution, ✨ enhancement

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% 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 clearly and specifically identifies the main change: a new resolvedPlaylistTopic feature for the Live Status Gateway.
Description check ✅ Passed The description is directly related to the changeset, providing context about the feature, motivations, and specifics of the new resolvedPlaylist subscription.

✏️ 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 17, 2026

@justandras justandras force-pushed the justandras/lsg-full-status branch from 1490d4d to 8253eeb Compare April 17, 2026 12:16
Copy link
Copy Markdown
Contributor

@Saftret Saftret left a comment

Choose a reason for hiding this comment

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

Functionality has been tested.

@rjmunro
Copy link
Copy Markdown
Contributor

rjmunro commented Apr 17, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 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: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/live-status-gateway/src/topics/helpers/notification/toNotificationStatus.ts (1)

19-27: ⚠️ Potential issue | 🔴 Critical

Incorrect mapping for NoteSeverity.INFO — returns ERROR instead of INFO.

Now that this helper is exported and reused by the new resolved-playlist conversion path (e.g. toResolvedPartStatus.ts mapping invalidReason), this pre-existing mapping bug becomes observable on a new externally-visible channel: any INFO-severity input will surface as ERROR to API consumers.

🐛 Proposed fix
 		case NoteSeverity.INFO:
-			return NotificationSeverity.ERROR
+			return NotificationSeverity.INFO
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/topics/helpers/notification/toNotificationStatus.ts`
around lines 19 - 27, The function toNotificationSeverity incorrectly maps
NoteSeverity.INFO to NotificationSeverity.ERROR; update the mapping in
toNotificationSeverity so that the case for NoteSeverity.INFO returns
NotificationSeverity.INFO (and consider adding a default/fallback branch or
explicit exhaustive check for NoteSeverity to avoid silent mis-mapping in the
future); locate and fix the switch in toNotificationSeverity that references
NoteSeverity and NotificationSeverity to correct the INFO->INFO mapping.
🧹 Nitpick comments (22)
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/resolvedPlaylistConversionTestUtils.ts (2)

96-123: makePieceInstance lacks partInstanceId.

Most consumers of PieceInstance filter or group by partInstanceId; fixtures without it may let conversion code appear to work in tests while missing real-world joins. Consider accepting/defaulting a partInstanceId parameter so piece instances can be associated with the part instances constructed by makePartInstance.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/resolvedPlaylistConversionTestUtils.ts`
around lines 96 - 123, makePieceInstance currently creates PieceInstance
fixtures without a partInstanceId, which breaks tests that filter/group by
partInstanceId; update makePieceInstance to accept an optional partInstanceId
parameter (or default to the id of the PartInstance created by makePartInstance)
and set it on the returned object as partInstanceId so consumers and conversion
logic that join/group by partInstanceId behave like real data (refer to
makePieceInstance, makePartInstance, and the PieceInstance.partInstanceId
field).

20-123: Consider typing factory return values instead of any.

Returning any from these factories means test suites won't catch drift when the core/API models change (added/renamed required fields). Using Partial<DBRundownPlaylist>-shaped builders (or DeepPartial) with a single as unknown as DBRundownPlaylist cast at the boundary would restore most of the type guarantees while keeping fixture ergonomics.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/resolvedPlaylistConversionTestUtils.ts`
around lines 20 - 123, The factories (makePlaylist, makeRundown, makeSegment,
makePart, makePartInstance, makePieceInstance) currently return any which loses
type-safety; change their return types to Partial or DeepPartial of the correct
DB model types (e.g. DBRundownPlaylist, DBRundown, DBSegment, DBPart,
DBPartInstance, DBPieceInstance) and type the overrides parameter accordingly
(overrides: Partial<DBRundownPlaylist> etc.), then at the function boundary cast
the final object with a single "as unknown as DBX" if needed; import the DB
types and/or a DeepPartial helper and replace the any return types and override
params for each factory.
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts (1)

169-178: Minor: string-selector branch for piecesFind / pieceInstancesFind may not match core semantics.

piecesFind(selector) in corelib typically expects a Mongo-style selector object, not a bare id string. If no caller passes a string, this branch is dead; if callers do, compare against unprotectString(p._id) as done in segmentsFindOne (Line 151) for consistency with ID normalization elsewhere in this file.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts`
around lines 169 - 178, The string-selector branches in piecesFind and
pieceInstancesFind currently compare raw _id values and may not match core
semantics; update both branches to normalize IDs using unprotectString like
segmentsFindOne does: when selector is a string, filter piecesInPlaylistState
and pieceInstancesInPlaylistState by comparing unprotectString(p._id) (or
unprotectString(pi._id)) to the selector, preserving the existing behavior for
object selectors and the empty-selector case.
packages/live-status-gateway-api/api/components/timing/resolvedPartTiming/resolvedPartTiming.yaml (1)

1-31: LGTM — schema aligns with converter output.

Required set (startMs, durationMs) matches non-nullable fields in toResolvedPartStatus, and optional timestamp fields are consistently populated (with 0 fallback) by the converter.

One consideration for a future iteration: since the converter coerces unset timing values to 0, consumers cannot distinguish "not yet occurred" from "occurred at epoch 0". If that distinction matters, consider making the optional timestamp properties nullable in the schema and propagating null from the converter. Not blocking.

🤖 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/timing/resolvedPartTiming/resolvedPartTiming.yaml`
around lines 1 - 31, The schema's optional timestamp properties
(plannedStartedPlayback, reportedStartedPlayback, playOffset, setAsNext, take)
are currently non-nullable numbers while the converter toResolvedPartStatus
coerces unset values to 0, losing "not yet occurred" vs epoch-0 distinction;
update the $defs.resolvedPartTiming property types for those optional timestamps
to allow null (e.g., type: [number, "null"]) and then modify the converter
function toResolvedPartStatus to propagate null for truly unset timing values
instead of 0 so consumers can distinguish absent timestamps; also update the
example reference (resolvedPartTiming-example.yaml) if needed to reflect nulls.
packages/live-status-gateway-api/api/components/part/partInvalidReason/partInvalidReason-example.yaml (1)

1-3: Consider verifying the color choice for "warning" severity.

The example uses red (#ff0000) for a warning severity level. Typically, red is associated with errors while warnings use orange or yellow. If this color mapping is intentional and aligns with your UI design system, this is fine.

🤖 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/part/partInvalidReason/partInvalidReason-example.yaml`
around lines 1 - 3, The example uses the color key alongside message and
severity (message: 'Invalid clip reference', severity: warning, color:
'#ff0000'); update the color to a warning-appropriate hex (e.g., '#FFA500' or
'#FFCC00') or confirm and document that red is intentional in the
partInvalidReason example by replacing the color value accordingly and/or adding
a comment that this maps to your design system.
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/serializeResolvedSegment.spec.ts (1)

3-47: Consider also asserting the stripped fields are absent.

serializePiece should not only produce the expected object but also guarantee outputLayer is absent (not just equal via toEqual, which in Jest does treat missing vs. undefined as equivalent for own enumerable properties). Consider a not.toHaveProperty('outputLayer') assertion to make the intent (transport-safety / cycle-stripping) explicit and robust against future shape changes.

Also consider adding a negative test for serializeSegmentExtended verifying that an unrelated top-level field (e.g. _id) is preserved, to lock in the "only layer graph is rewritten" contract.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/serializeResolvedSegment.spec.ts`
around lines 3 - 47, Update the tests to explicitly assert removed fields are
absent and preserved fields remain: in the serializePiece test (function
serializePiece) add expect(result).not.toHaveProperty('outputLayer') to ensure
outputLayer is removed rather than undefined, and keep the existing equality
check for transport-safe fields; in the serializePart test ensure nested piece
fields are properly serialized (serializePart and the pieces assertion is fine);
and add a negative/preservation assertion to serializeSegmentExtended: assert
expect(result).toHaveProperty('_id', 'segment0') (or similar) to confirm
top-level fields like _id are preserved while outputLayers.ol1.sourceLayers is
stripped to [] and sourceLayers.sl1.pieces becomes ['pieceA','pieceB'].
packages/live-status-gateway-api/api/components/layers/sourceLayer/sourceLayer.yaml (1)

19-24: Consider integer + enum for type, and integer for rank.

type is described as a "numeric enum" but typed as number with no constraints; downstream clients can't generate a proper enum. The SourceLayerType enum defines a specific integer set (0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 13, 14, 15, 16, 17), so prefer type: integer plus an enum list. Similarly, rank should use type: integer instead of number. Neither change is blocking.

🤖 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/layers/sourceLayer/sourceLayer.yaml`
around lines 19 - 24, Change the OpenAPI schema for the source layer so the
"type" property uses integer with an explicit enum list matching the
SourceLayerType values and the "rank" property uses type: integer instead of
number; specifically, update the properties named type and rank in
sourceLayer.yaml to use type: integer, and add an enum array for type containing
the SourceLayerType integer values (0,1,2,3,4,5,6,7,10,11,13,14,15,16,17) so
downstream clients can generate a proper enum.
packages/live-status-gateway-api/api/components/timing/resolvedSegmentTiming/resolvedSegmentTiming.yaml (1)

7-15: Consider type: integer for millisecond fields.

startMs, endMs, and budgetDurationMs are declared as number, which permits fractional values. Millisecond timing values elsewhere in the LSG API are typically integers; using integer here would catch accidental float emissions in generated consumers and better match the wire format. Non-blocking.

🤖 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/timing/resolvedSegmentTiming/resolvedSegmentTiming.yaml`
around lines 7 - 15, Change the three timing fields to use integer types instead
of number: update startMs, endMs, and budgetDurationMs in
resolvedSegmentTiming.yaml so their schema entries use "type: integer"
(preserving their descriptions) to ensure millisecond values are validated as
whole milliseconds and catch accidental fractional values in generated
consumers.
packages/live-status-gateway-api/api/components/part/partInvalidReason/partInvalidReason.yaml (1)

13-15: Consider constraining color format.

color is currently an unbounded string. If this is meant to be a CSS color (as the example #ff0000 suggests), consider adding a pattern or at least documenting the accepted formats (hex, rgb(), named) so client renderers can rely on it. Safe to defer.

🤖 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/part/partInvalidReason/partInvalidReason.yaml`
around lines 13 - 15, The `color` property in the PartInvalidReason schema is an
unconstrained string; update the `color` field definition in
partInvalidReason.yaml to constrain or document accepted formats (e.g., add a
`pattern` that accepts hex colors like `#RRGGBB` and `#RGB` and/or rgb(a)
functions, or add a `oneOf`/`enum` or descriptive `description` listing
supported formats) so client renderers can reliably parse values; target the
`color` property in the PartInvalidReason component when making this change.
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/toResolvedSegmentStatus.spec.ts (1)

15-17: Missing jest.resetAllMocks() / mockClear between tests.

Only one test exists today, so this is not a real issue, but the mocked getResolvedSegment will retain call history and the mockReturnValue across future test additions in this file. Consider adding beforeEach(() => jest.clearAllMocks()) now to avoid surprises when the suite grows.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/toResolvedSegmentStatus.spec.ts`
around lines 15 - 17, The test file currently mocks getResolvedSegment
(jest.mock(... getResolvedSegment ...)) but doesn't reset or clear mocks between
tests; add a beforeEach hook (e.g., beforeEach(() => jest.clearAllMocks()) or
jest.resetAllMocks()) at the top of the spec so getResolvedSegment's call
history and mockReturnValue are cleared before each test to avoid cross-test
leakage.
packages/live-status-gateway-api/api/components/segment/resolvedSegment/resolvedSegment.yaml (1)

7-10: Remove the duplicated title and description from the inner allOf branch.

Lines 8-9 repeat the outer title: ResolvedSegment and description: A segment within a resolved rundown from lines 3-4. Per-branch titles inside allOf can confuse schema-to-type generators. Removing them will keep the schema clean and prevent potential issues.

Proposed cleanup
     allOf:
       - $ref: '../segmentBase/segmentBase.yaml#/$defs/segmentBase'
       - type: object
-        title: ResolvedSegment
-        description: A segment within a resolved rundown
         properties:
🤖 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/segment/resolvedSegment/resolvedSegment.yaml`
around lines 7 - 10, The inner allOf branch of the ResolvedSegment schema
contains duplicated metadata; remove the redundant "title: ResolvedSegment" and
"description: A segment within a resolved rundown" from the nested allOf entry
so only the outer object retains title/description (look for the ResolvedSegment
schema and its inner allOf block) to avoid confusing schema-to-type generators;
leave all other properties in the inner branch intact.
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/rundowns/toResolvedRundownStatus.ts (1)

7-7: Redundant String(rundownId) coercion.

rundownId is already typed as string, so String(...) is a no-op on both line 7 and line 9. Can drop the wrapping for readability.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/rundowns/toResolvedRundownStatus.ts`
at line 7, Remove the redundant String(...) coercions around rundownId in
toResolvedRundownStatus: change ctx.rundownsById.get(String(rundownId)) and any
other occurrences (e.g., the similar call on line 9) to use
ctx.rundownsById.get(rundownId) (and .has(rundownId) if present), since
rundownId is already typed as string; this improves readability without changing
behavior.
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/conversionContext.spec.ts (1)

72-85: Consider also asserting the missing showStyleBaseExt path.

The test title says "throws when required playlist/showStyle is missing" but only exercises the missing playlistState case. A second assertion with showStyleBaseExtState: undefined would fully cover the intended contract.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/conversionContext.spec.ts`
around lines 72 - 85, The test currently only checks the missing playlist case
for createResolvedPlaylistConversionContext; add a second assertion that keeps
playlistState defined (e.g., use makeTestRundownPlaylist or the existing valid
playlist fixture) and sets showStyleBaseExtState: undefined to ensure it also
throws the same 'Missing playlist or showStyleBaseExt' error; update the spec
'throws when required playlist/showStyle is missing' to include both
expectations so both failure paths (playlistState missing and
showStyleBaseExtState missing) are verified.
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/toResolvedPieceStatus.spec.ts (1)

58-62: Minor duplication: makePieceInstance('inst3') is invoked twice.

Consider extracting to a local const for clarity:

♻️ Suggested refactor
-	it('filters invalid abSessions entries', () => {
-		const pieceExtended = {
-			instance: {
-				...makePieceInstance('inst3'),
-				piece: {
-					...makePieceInstance('inst3').piece,
-					abSessions: [{ poolName: 'ok', sessionName: 'ok', playerId: 'p0' }, { poolName: 'bad' }],
-				},
-			},
-		} as any
+	it('filters invalid abSessions entries', () => {
+		const baseInstance = makePieceInstance('inst3')
+		const pieceExtended = {
+			instance: {
+				...baseInstance,
+				piece: {
+					...baseInstance.piece,
+					abSessions: [{ poolName: 'ok', sessionName: 'ok', playerId: 'p0' }, { poolName: 'bad' }],
+				},
+			},
+		} as any
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/toResolvedPieceStatus.spec.ts`
around lines 58 - 62, Duplicate calls to makePieceInstance('inst3') should be
collapsed: create a local const (e.g., const inst3 = makePieceInstance('inst3'))
and reuse inst3 and inst3.piece in the test data instead of calling
makePieceInstance('inst3') twice; update the array entries that currently spread
makePieceInstance('inst3') and makePieceInstance('inst3').piece to use the new
inst3 variable.
packages/live-status-gateway/src/topics/resolvedPlaylistTopic.ts (1)

40-48: Consider narrower types on the cached arrays.

_partInstancesInPlaylist uses DBPartInstance while the send path casts to PartInstance. If the downstream pipeline expects PartInstance, updating the field type to match what the handler actually emits would eliminate the as PartInstance[] cast in sendStatus and let the compiler catch future shape drift.

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

In `@packages/live-status-gateway/src/topics/resolvedPlaylistTopic.ts` around
lines 40 - 48, The cached array _partInstancesInPlaylist is typed as
DBPartInstance[] but sendStatus casts it to PartInstance[]; change the field
declaration of _partInstancesInPlaylist to PartInstance[] (or convert/mapper
when populating it) so the stored type matches what sendStatus emits and remove
the unnecessary as PartInstance[] cast; update any places that push/pop into
_partInstancesInPlaylist (e.g., assignment sites in ResolvedPlaylistTopic
methods) to produce/convert DBPartInstance -> PartInstance if needed so the
compiler enforces shape compatibility.
packages/live-status-gateway-api/api/components/resolvedPlaylist/resolvedPlaylistEvent/resolvedPlaylistEvent.yaml (1)

26-29: playoutState and publicData have no type declaration.

Unlike the other properties these are declared with only a description, so anything (including arrays/strings) validates. If the intent is "arbitrary object blob" (as the runtime populates them with {}), consider pinning them to type: object with additionalProperties: true for consistency with other components. Non-blocking.

🤖 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/resolvedPlaylist/resolvedPlaylistEvent/resolvedPlaylistEvent.yaml`
around lines 26 - 29, The properties playoutState and publicData lack type
declarations; update their schema entries (playoutState and publicData) to be
explicit object blobs by adding "type: object" and "additionalProperties: true"
so they validate consistently with other components and allow arbitrary
keys/values.
packages/live-status-gateway-api/api/components/part/resolvedPart/resolvedPart.yaml (1)

1-56: Schema matches the converter output; minor consistency nit on publicData.

required list covers the new invalid/floated/untimed flags, and state is correctly left optional (only set for the current/next part). Like resolvedPlaylistEvent.yaml, publicData is declared with only a description; consider type: object with additionalProperties: true for consistency across components. Non-blocking.

🤖 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/part/resolvedPart/resolvedPart.yaml`
around lines 1 - 56, The publicData property in the resolvedPart schema is only
documented with a description; update the resolvedPart properties block
(property name: publicData) to declare it as an open object by adding "type:
object" and "additionalProperties: true" so it matches other components (e.g.,
resolvedPlaylistEvent) and accepts arbitrary key/value pairs.
packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts (1)

83-86: Direct updateAndNotify() bypasses the throttle.

changed() routes through _throttledUpdateAndNotify, but this branch calls updateAndNotify() synchronously. With frequent playlist updates (e.g. activation id churn on startup), this can emit redundant notifications that would otherwise be coalesced. Consider going through the throttled path for consistency:

Proposed fix
-			} else if (this._subscriptionId) {
-				this.updateAndNotify()
+			} else if (this._subscriptionId) {
+				this._throttledUpdateAndNotify()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts`
around lines 83 - 86, The branch that currently calls updateAndNotify() directly
bypasses the throttling used elsewhere; replace the direct call with
this._throttledUpdateAndNotify() so the changed() path consistently coalesces
frequent updates (ensure _throttledUpdateAndNotify is called on this and that
its binding/initialization matches other usages of the throttled updater).
packages/live-status-gateway/src/collections/pieceInstancesInPlaylistHandler.ts (2)

56-60: any cast obscures DBPartInstance shape from upstream handler.

data.all is already typed as DBPartInstance[] via PartInstancesInPlaylist. The (pi: any) cast is unnecessary and loses type-checking on _id. Small cleanup:

Proposed refactor
-		this._partInstanceIds = _.compact((data?.all ?? []).map((pi: any) => pi._id)).sort()
+		this._partInstanceIds = _.compact((data?.all ?? []).map((pi) => pi._id)).sort()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/collections/pieceInstancesInPlaylistHandler.ts`
around lines 56 - 60, The onPartInstancesInPlaylistUpdate handler uses an
unnecessary (pi: any) cast which hides the DBPartInstance shape; remove the any
cast so TypeScript uses PartInstancesInPlaylist typing (or explicitly type as
DBPartInstance) and map to pi._id directly (e.g. (data?.all ?? []).map(pi =>
pi._id)), preserving the rest of the logic that assigns this._partInstanceIds
and calls resubscribe(this._currentRundownIds, prevPartInstanceIds); ensure the
mapped type matches this._partInstanceIds (string | undefined) and keep the
_.compact(...).sort() pipeline intact.

62-92: resubscribe never re-evaluates when only _partInstanceIds shrunk to empty after a prior active subscription — consider explicit ordering.

The two early-return branches (Lines 67-71 and 73-77) both call stopSubscription() + clearAndNotify(), which is correct. However, the following branch at Line 84 also calls stopSubscription() before setupSubscription(). Note that setupSubscription in the base already calls stopSubscription() (see the base class), so the explicit one here is redundant. Keeping it isn't wrong — just worth noting for consistency with piecesInPlaylistHandler/partInstancesInPlaylistHandler, which omit it.

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

In
`@packages/live-status-gateway/src/collections/pieceInstancesInPlaylistHandler.ts`
around lines 62 - 92, Move the emptiness checks for this._currentRundownIds and
this._partInstanceIds to occur before computing sameSubscription so shrinking to
empty re-evaluates correctly; then when recreating the observer in resubscribe
use setupSubscription(this._currentRundownIds, this._partInstanceIds, {})
without the redundant stopSubscription() (setupSubscription already stops
existing subscriptions). Reference: resubscribe, this._currentRundownIds,
this._partInstanceIds, sameSubscription, stopSubscription, setupSubscription,
updateAndNotify, clearAndNotify (align behavior with
piecesInPlaylistHandler/partInstancesInPlaylistHandler).
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/events/toResolvedPlaylistStatus.ts (1)

73-82: segmentsById/partsById built but only forwarded to toQuickLoopStatus.

These maps are re-derived every event emission, duplicating work already done inside createResolvedPlaylistConversionContext (which presumably builds segmentsByRundownId/rundown/segment/part lookups). If the context already exposes per-id maps, reuse them to avoid the extra O(N) allocations on every publish, or have toQuickLoopStatus accept the context directly.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/events/toResolvedPlaylistStatus.ts`
around lines 73 - 82, The code in toResolvedPlaylistStatus builds segmentsById
and partsById on every event but createResolvedPlaylistConversionContext already
creates per-id lookups; remove the O(N) reconstructions and reuse the
context-provided maps (or change toQuickLoopStatus to accept the context
directly). Specifically, stop mapping (segmentsState ?? []).map(...) and
(partsState ?? []).map(...), and instead reference the lookup objects from the
conversion context returned by createResolvedPlaylistConversionContext (or pass
that context into toQuickLoopStatus and read its segmentsById/partsById there)
so the existing precomputed maps are reused.
packages/live-status-gateway/src/collections/piecesInPlaylistHandler.ts (1)

49-56: sameSubscription mixes boolean and SubscriptionId; hard to read.

_.isEqual(prevRundownIds, rundownIds) && this._subscriptionId yields either false or a SubscriptionId string. It works under truthiness, but the intent is clearer as an explicit boolean, and avoids confusing the subsequent else if (this._subscriptionId) which then becomes dead code (reachable only when both isEqual===true and _subscriptionId is falsy, in which case !sameSubscription is true and the earlier branch already handles it).

Proposed refactor
-		if (rundownIds && rundownIds.length) {
-			const sameSubscription = _.isEqual(prevRundownIds, rundownIds) && this._subscriptionId
-			if (!sameSubscription) {
-				this.setupSubscription(rundownIds, null)
-			} else if (this._subscriptionId) {
-				this.updateAndNotify()
-			} else {
-				this.clearAndNotify()
-			}
-		} else {
-			this.clearAndNotify()
-		}
+		if (rundownIds && rundownIds.length) {
+			const sameScope = _.isEqual(prevRundownIds, rundownIds)
+			if (!sameScope || !this._subscriptionId) {
+				this.setupSubscription(rundownIds, null)
+			} else {
+				this._throttledUpdateAndNotify()
+			}
+		} else {
+			this.stopSubscription()
+			this.clearAndNotify()
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/live-status-gateway/src/collections/piecesInPlaylistHandler.ts`
around lines 49 - 56, Replace the mixed-typed sameSubscription expression with
two explicit booleans (e.g. hasSameIds = _.isEqual(prevRundownIds, rundownIds)
and hasSubscription = !!this._subscriptionId) and then branch using those: if
!hasSameIds call setupSubscription(rundownIds, null), else if hasSubscription
call updateAndNotify(), else call clearAndNotify(); reference symbols:
prevRundownIds, rundownIds, _subscriptionId, setupSubscription, updateAndNotify,
clearAndNotify.
🤖 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/part/resolvedPart/resolvedPart-example.yaml`:
- Around line 1-17: The example file resolvedPart-example.yaml currently uses a
root-level $ref to partBase-example.yaml with sibling properties which violates
JSON Schema composition semantics; update the example to match the schema's
allOf composition by replacing the root $ref with an allOf array that includes a
$ref to '../partBase/partBase-example.yaml' and a second object containing the
sibling properties (instanceId, externalId, rank, invalid, floated, untimed,
invalidReason ref, state, createdByAdLib, publicData, timing ref, pieces refs),
or alternatively inline all fields from partBase into this file so there is no
root $ref with siblings; ensure references to
'../partInvalidReason/partInvalidReason-example.yaml',
'../../timing/resolvedPartTiming/resolvedPartTiming-example.yaml', and
'../../piece/resolvedPiece/resolvedPiece-example.yaml' remain intact.

In
`@packages/live-status-gateway-api/api/components/rundown/resolvedRundown/resolvedRundown.yaml`:
- Around line 19-29: The schema lists the "description" property as "Optional
description" but also includes it in the required array; decide which is correct
and make the schema consistent: either remove "description" from the required
list (update required: [id, externalId, name, rank, segments]) if it's optional,
or change the property description text for "description" to indicate it is
always present (e.g., "Description (may be empty)") if it must remain required;
update the resolvedRundown.yaml required array and the "description" property's
description accordingly and leave publicData as-is.

In
`@packages/live-status-gateway-api/api/components/tTimers/timerStateRunning-example.yaml`:
- Around line 1-3: The example for the "running" timer has an inconsistent
state: the fields paused and pauseTime conflict (paused: false but pauseTime:
1706371900000). Update the timerStateRunning example so that when paused is
false the pauseTime is null (or remove the pauseTime field) to match the
countdownRunning example and represent an active/running timer; change the
pauseTime value in timerStateRunning-example.yaml accordingly.

In
`@packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts`:
- Around line 77-91: When the playlist deactivates or _rundownIds becomes empty
the code calls clearAndNotify() but never stops the server observer; modify the
else branch (the branch where !(this._currentPlaylist && this._rundownIds.length
&& this._activationId)) to call stopSubscription() before clearAndNotify() (or
at least when there was an active subscription previously, e.g., when
prevActivationId and prevRundownIds exist or this._subscriptionId is set) so
that setupSubscription/stopSubscription usage matches the logic in
PieceInstancesInPlaylistHandler.resubscribe and avoids leaking observers.

In `@packages/live-status-gateway/src/collections/piecesInPlaylistHandler.ts`:
- Around line 43-60: onPlaylistUpdate currently leaks subscriptions when
playlist becomes empty and calls updateAndNotify directly on rapid document
events; ensure you call stopSubscription() before clearing the subscription
scope (i.e. before or inside the branch that previously only called
clearAndNotify()) so the server-side pieces observer is stopped when
rundownIdsInOrder is empty/undefined, and replace direct calls to
updateAndNotify() with a throttled invocation using throttleToNextTick (same
utility used in PartInstancesInPlaylistHandler) so onDocumentEvent bursts don’t
emit repeatedly; refer to onPlaylistUpdate, setupSubscription, stopSubscription,
clearAndNotify, updateAndNotify and _subscriptionId to locate where to add
stopSubscription() and where to wrap updateAndNotify with throttleToNextTick.

In
`@packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/toTTimers.ts`:
- Around line 30-37: toTTimers currently assumes tTimers has three positional
entries and calls toTTimer on tTimers[0..2], which crashes when length is 1–2
and misplaces timers when entries aren’t in index order; change toTTimers to
build a fixed-length array for indexes 1..3 by finding a RundownTTimer with
matching index for each slot (use the index field on RundownTTimer), call
toTTimer only on the found element, and fall back to emptyTimer(slotIndex) when
no matching entry exists; ensure toTTimer and emptyTimer are used as helpers and
that extra entries with indexes >3 are ignored or logged but not placed by
position.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts`:
- Around line 107-111: getOrderedPartsInRundown sorts all parts in a rundown by
DBPart._rank which is scoped per segment, causing interleaving across segments;
change the implementation to iterate the rundown's segments from
ctx.segmentsByRundownId (in rundown order), for each segment gather parts from
ctx.partsByRundownId that match the segment._id, sort those by _rank, and append
them to the result array so parts are returned in proper rundown (segment)
order; update the function getOrderedPartsInRundown to use segmentsByRundownId,
filter by segmentId, sort per-segment, and concatenate.
- Around line 164-168: getActivePartInstances currently returns
partInstancesInPlaylistState without excluding reset instances; update the
implementation of getActivePartInstances to merge a reset: { $ne: true }
condition into the incoming selector (or use it alone when selector is
undefined) and then pass the merged selector to mongoWhereFilter so reset
instances are excluded, matching the StateCacheResolverDataAccess contract and
the webui reference behavior.
- Around line 58-60: The map rundownsToShowStyles currently assigns every
rundown ID to showStyleBaseExt._id; instead iterate playlist.rundownIdsInOrder
and for each rundown id look up its DBRundown and use that rundown's
showStyleBaseId (DBRundown.showStyleBaseId) when creating the Map (fall back to
showStyleBaseExt._id only if the DBRundown is missing), i.e. replace the current
(playlist.rundownIdsInOrder ?? []).map((r) => [r, showStyleBaseExt._id]) with
logic that resolves each r to its own rundown.showStyleBaseId.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/events/toResolvedPlaylistStatus.ts`:
- Around line 99-100: The fields playoutState and publicData may be undefined in
the non-fallback branch; update the object construction in
toResolvedPlaylistStatus (where playoutState:
ctx.playlist.publicPlayoutPersistentState and publicData:
ctx.playlist.publicData are assigned) to coalesce undefined to an empty object
(e.g. use `?? {}`) so the serialized shape always includes those keys and
matches the fallback shape; ensure both playoutState and publicData use the same
nullish-coalescing pattern.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/parts/toResolvedPartStatus.ts`:
- Around line 30-54: The id can become the literal "undefined" when both
part.partId and basePart._id are missing; update the id expression in
toResolvedPartStatus to provide a safe fallback before calling unprotectString
(e.g., use unprotectString(part.partId ?? basePart._id ?? '') ) so
unprotectString never receives undefined; locate the id creation in the returned
object (the line using unprotectString and references to part.partId and
basePart._id) and replace it with the fallback expression.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/pieces/toResolvedPieceStatus.ts`:
- Around line 15-17: The instanceId currently calls
unprotectString(instance?._id) which can receive undefined and produce the
literal "undefined"; update the returned instanceId so it only calls
unprotectString when instance exists (mirror how id is handled): check instance
(or instance._id) and return unprotectString(instance._id) when present,
otherwise return undefined (or null if the surrounding code expects that).
Modify the return in toResolvedPieceStatus (the instanceId field) accordingly.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/rundowns/toResolvedRundownStatus.ts`:
- Line 9: The code currently computes rank with Math.max(0,
ctx.orderedRundownIds.indexOf(String(rundownId))) which collapses a missing
rundown (index -1) to 0; change this to explicitly handle the -1 case in
toResolvedRundownStatus (or the enclosing function) by checking const idx =
ctx.orderedRundownIds.indexOf(String(rundownId)); if (idx === -1) set rank to a
sentinel (e.g. ctx.orderedRundownIds.length) or undefined and propagate that
value (or return early) so missing rundowns are distinguishable from the first
rundown; update any downstream logic that reads rank accordingly.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/segments/serializeResolvedSegment.ts`:
- Around line 15-25: serializePart dereferences partExtended directly which can
throw if a falsy value is passed; mirror serializePiece's guard by defaulting
partExtended to an empty object before accessing fields (e.g., treat part =
partExtended ?? {}), then use optional chaining (part.partId, part.instance,
etc.) and keep the pieces mapping as is to ensure consistent null-safe behavior
with serializePiece and other serializers.

In `@packages/live-status-gateway/src/topics/resolvedPlaylistTopic.ts`:
- Around line 64-77: The sendStatus method currently calls
toResolvedPlaylistStatus with this._playlist and this._showStyleBaseExt even
when they may be undefined; add an early-return guard at the top of sendStatus
that checks required state (e.g. this._playlist and this._showStyleBaseExt) and
returns immediately if either is missing to avoid calling
toResolvedPlaylistStatus (which triggers createResolvedPlaylistConversionContext
and can throw). Locate sendStatus in resolvedPlaylistTopic.ts and mirror the
existing pattern used in other topics (early-return when required inputs are
absent) so throttledSendStatusToAll won't propagate exceptions.
- Line 45: _field _partInstancesInPlaylist is typed as DBPartInstance[] but
later cast to PartInstance[] (which adds isTemporary), creating an unsafe
mismatch; fix by either changing the storage to real PartInstance objects or
stop the cast and update consumers to accept DBPartInstance[]. Concretely,
update the declaration of _partInstancesInPlaylist to PartInstance[] and ensure
wherever items are inserted into _partInstancesInPlaylist (and any
factory/mapper functions) they are given an explicit isTemporary boolean, or
alternatively remove the cast to PartInstance[] where used and change
function/method signatures that consume it to accept DBPartInstance[]
(referencing _partInstancesInPlaylist, DBPartInstance, PartInstance, and the
place where the cast currently occurs) so types align without unsafe casts.

---

Outside diff comments:
In
`@packages/live-status-gateway/src/topics/helpers/notification/toNotificationStatus.ts`:
- Around line 19-27: The function toNotificationSeverity incorrectly maps
NoteSeverity.INFO to NotificationSeverity.ERROR; update the mapping in
toNotificationSeverity so that the case for NoteSeverity.INFO returns
NotificationSeverity.INFO (and consider adding a default/fallback branch or
explicit exhaustive check for NoteSeverity to avoid silent mis-mapping in the
future); locate and fix the switch in toNotificationSeverity that references
NoteSeverity and NotificationSeverity to correct the INFO->INFO mapping.

---

Nitpick comments:
In
`@packages/live-status-gateway-api/api/components/layers/sourceLayer/sourceLayer.yaml`:
- Around line 19-24: Change the OpenAPI schema for the source layer so the
"type" property uses integer with an explicit enum list matching the
SourceLayerType values and the "rank" property uses type: integer instead of
number; specifically, update the properties named type and rank in
sourceLayer.yaml to use type: integer, and add an enum array for type containing
the SourceLayerType integer values (0,1,2,3,4,5,6,7,10,11,13,14,15,16,17) so
downstream clients can generate a proper enum.

In
`@packages/live-status-gateway-api/api/components/part/partInvalidReason/partInvalidReason-example.yaml`:
- Around line 1-3: The example uses the color key alongside message and severity
(message: 'Invalid clip reference', severity: warning, color: '#ff0000'); update
the color to a warning-appropriate hex (e.g., '#FFA500' or '#FFCC00') or confirm
and document that red is intentional in the partInvalidReason example by
replacing the color value accordingly and/or adding a comment that this maps to
your design system.

In
`@packages/live-status-gateway-api/api/components/part/partInvalidReason/partInvalidReason.yaml`:
- Around line 13-15: The `color` property in the PartInvalidReason schema is an
unconstrained string; update the `color` field definition in
partInvalidReason.yaml to constrain or document accepted formats (e.g., add a
`pattern` that accepts hex colors like `#RRGGBB` and `#RGB` and/or rgb(a)
functions, or add a `oneOf`/`enum` or descriptive `description` listing
supported formats) so client renderers can reliably parse values; target the
`color` property in the PartInvalidReason component when making this change.

In
`@packages/live-status-gateway-api/api/components/part/resolvedPart/resolvedPart.yaml`:
- Around line 1-56: The publicData property in the resolvedPart schema is only
documented with a description; update the resolvedPart properties block
(property name: publicData) to declare it as an open object by adding "type:
object" and "additionalProperties: true" so it matches other components (e.g.,
resolvedPlaylistEvent) and accepts arbitrary key/value pairs.

In
`@packages/live-status-gateway-api/api/components/resolvedPlaylist/resolvedPlaylistEvent/resolvedPlaylistEvent.yaml`:
- Around line 26-29: The properties playoutState and publicData lack type
declarations; update their schema entries (playoutState and publicData) to be
explicit object blobs by adding "type: object" and "additionalProperties: true"
so they validate consistently with other components and allow arbitrary
keys/values.

In
`@packages/live-status-gateway-api/api/components/segment/resolvedSegment/resolvedSegment.yaml`:
- Around line 7-10: The inner allOf branch of the ResolvedSegment schema
contains duplicated metadata; remove the redundant "title: ResolvedSegment" and
"description: A segment within a resolved rundown" from the nested allOf entry
so only the outer object retains title/description (look for the ResolvedSegment
schema and its inner allOf block) to avoid confusing schema-to-type generators;
leave all other properties in the inner branch intact.

In
`@packages/live-status-gateway-api/api/components/timing/resolvedPartTiming/resolvedPartTiming.yaml`:
- Around line 1-31: The schema's optional timestamp properties
(plannedStartedPlayback, reportedStartedPlayback, playOffset, setAsNext, take)
are currently non-nullable numbers while the converter toResolvedPartStatus
coerces unset values to 0, losing "not yet occurred" vs epoch-0 distinction;
update the $defs.resolvedPartTiming property types for those optional timestamps
to allow null (e.g., type: [number, "null"]) and then modify the converter
function toResolvedPartStatus to propagate null for truly unset timing values
instead of 0 so consumers can distinguish absent timestamps; also update the
example reference (resolvedPartTiming-example.yaml) if needed to reflect nulls.

In
`@packages/live-status-gateway-api/api/components/timing/resolvedSegmentTiming/resolvedSegmentTiming.yaml`:
- Around line 7-15: Change the three timing fields to use integer types instead
of number: update startMs, endMs, and budgetDurationMs in
resolvedSegmentTiming.yaml so their schema entries use "type: integer"
(preserving their descriptions) to ensure millisecond values are validated as
whole milliseconds and catch accidental fractional values in generated
consumers.

In
`@packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts`:
- Around line 83-86: The branch that currently calls updateAndNotify() directly
bypasses the throttling used elsewhere; replace the direct call with
this._throttledUpdateAndNotify() so the changed() path consistently coalesces
frequent updates (ensure _throttledUpdateAndNotify is called on this and that
its binding/initialization matches other usages of the throttled updater).

In
`@packages/live-status-gateway/src/collections/pieceInstancesInPlaylistHandler.ts`:
- Around line 56-60: The onPartInstancesInPlaylistUpdate handler uses an
unnecessary (pi: any) cast which hides the DBPartInstance shape; remove the any
cast so TypeScript uses PartInstancesInPlaylist typing (or explicitly type as
DBPartInstance) and map to pi._id directly (e.g. (data?.all ?? []).map(pi =>
pi._id)), preserving the rest of the logic that assigns this._partInstanceIds
and calls resubscribe(this._currentRundownIds, prevPartInstanceIds); ensure the
mapped type matches this._partInstanceIds (string | undefined) and keep the
_.compact(...).sort() pipeline intact.
- Around line 62-92: Move the emptiness checks for this._currentRundownIds and
this._partInstanceIds to occur before computing sameSubscription so shrinking to
empty re-evaluates correctly; then when recreating the observer in resubscribe
use setupSubscription(this._currentRundownIds, this._partInstanceIds, {})
without the redundant stopSubscription() (setupSubscription already stops
existing subscriptions). Reference: resubscribe, this._currentRundownIds,
this._partInstanceIds, sameSubscription, stopSubscription, setupSubscription,
updateAndNotify, clearAndNotify (align behavior with
piecesInPlaylistHandler/partInstancesInPlaylistHandler).

In `@packages/live-status-gateway/src/collections/piecesInPlaylistHandler.ts`:
- Around line 49-56: Replace the mixed-typed sameSubscription expression with
two explicit booleans (e.g. hasSameIds = _.isEqual(prevRundownIds, rundownIds)
and hasSubscription = !!this._subscriptionId) and then branch using those: if
!hasSameIds call setupSubscription(rundownIds, null), else if hasSubscription
call updateAndNotify(), else call clearAndNotify(); reference symbols:
prevRundownIds, rundownIds, _subscriptionId, setupSubscription, updateAndNotify,
clearAndNotify.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/conversionContext.spec.ts`:
- Around line 72-85: The test currently only checks the missing playlist case
for createResolvedPlaylistConversionContext; add a second assertion that keeps
playlistState defined (e.g., use makeTestRundownPlaylist or the existing valid
playlist fixture) and sets showStyleBaseExtState: undefined to ensure it also
throws the same 'Missing playlist or showStyleBaseExt' error; update the spec
'throws when required playlist/showStyle is missing' to include both
expectations so both failure paths (playlistState missing and
showStyleBaseExtState missing) are verified.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/resolvedPlaylistConversionTestUtils.ts`:
- Around line 96-123: makePieceInstance currently creates PieceInstance fixtures
without a partInstanceId, which breaks tests that filter/group by
partInstanceId; update makePieceInstance to accept an optional partInstanceId
parameter (or default to the id of the PartInstance created by makePartInstance)
and set it on the returned object as partInstanceId so consumers and conversion
logic that join/group by partInstanceId behave like real data (refer to
makePieceInstance, makePartInstance, and the PieceInstance.partInstanceId
field).
- Around line 20-123: The factories (makePlaylist, makeRundown, makeSegment,
makePart, makePartInstance, makePieceInstance) currently return any which loses
type-safety; change their return types to Partial or DeepPartial of the correct
DB model types (e.g. DBRundownPlaylist, DBRundown, DBSegment, DBPart,
DBPartInstance, DBPieceInstance) and type the overrides parameter accordingly
(overrides: Partial<DBRundownPlaylist> etc.), then at the function boundary cast
the final object with a single "as unknown as DBX" if needed; import the DB
types and/or a DeepPartial helper and replace the any return types and override
params for each factory.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/serializeResolvedSegment.spec.ts`:
- Around line 3-47: Update the tests to explicitly assert removed fields are
absent and preserved fields remain: in the serializePiece test (function
serializePiece) add expect(result).not.toHaveProperty('outputLayer') to ensure
outputLayer is removed rather than undefined, and keep the existing equality
check for transport-safe fields; in the serializePart test ensure nested piece
fields are properly serialized (serializePart and the pieces assertion is fine);
and add a negative/preservation assertion to serializeSegmentExtended: assert
expect(result).toHaveProperty('_id', 'segment0') (or similar) to confirm
top-level fields like _id are preserved while outputLayers.ol1.sourceLayers is
stripped to [] and sourceLayers.sl1.pieces becomes ['pieceA','pieceB'].

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/toResolvedPieceStatus.spec.ts`:
- Around line 58-62: Duplicate calls to makePieceInstance('inst3') should be
collapsed: create a local const (e.g., const inst3 = makePieceInstance('inst3'))
and reuse inst3 and inst3.piece in the test data instead of calling
makePieceInstance('inst3') twice; update the array entries that currently spread
makePieceInstance('inst3') and makePieceInstance('inst3').piece to use the new
inst3 variable.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/toResolvedSegmentStatus.spec.ts`:
- Around line 15-17: The test file currently mocks getResolvedSegment
(jest.mock(... getResolvedSegment ...)) but doesn't reset or clear mocks between
tests; add a beforeEach hook (e.g., beforeEach(() => jest.clearAllMocks()) or
jest.resetAllMocks()) at the top of the spec so getResolvedSegment's call
history and mockReturnValue are cleared before each test to avoid cross-test
leakage.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts`:
- Around line 169-178: The string-selector branches in piecesFind and
pieceInstancesFind currently compare raw _id values and may not match core
semantics; update both branches to normalize IDs using unprotectString like
segmentsFindOne does: when selector is a string, filter piecesInPlaylistState
and pieceInstancesInPlaylistState by comparing unprotectString(p._id) (or
unprotectString(pi._id)) to the selector, preserving the existing behavior for
object selectors and the empty-selector case.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/events/toResolvedPlaylistStatus.ts`:
- Around line 73-82: The code in toResolvedPlaylistStatus builds segmentsById
and partsById on every event but createResolvedPlaylistConversionContext already
creates per-id lookups; remove the O(N) reconstructions and reuse the
context-provided maps (or change toQuickLoopStatus to accept the context
directly). Specifically, stop mapping (segmentsState ?? []).map(...) and
(partsState ?? []).map(...), and instead reference the lookup objects from the
conversion context returned by createResolvedPlaylistConversionContext (or pass
that context into toQuickLoopStatus and read its segmentsById/partsById there)
so the existing precomputed maps are reused.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/rundowns/toResolvedRundownStatus.ts`:
- Line 7: Remove the redundant String(...) coercions around rundownId in
toResolvedRundownStatus: change ctx.rundownsById.get(String(rundownId)) and any
other occurrences (e.g., the similar call on line 9) to use
ctx.rundownsById.get(rundownId) (and .has(rundownId) if present), since
rundownId is already typed as string; this improves readability without changing
behavior.

In `@packages/live-status-gateway/src/topics/resolvedPlaylistTopic.ts`:
- Around line 40-48: The cached array _partInstancesInPlaylist is typed as
DBPartInstance[] but sendStatus casts it to PartInstance[]; change the field
declaration of _partInstancesInPlaylist to PartInstance[] (or convert/mapper
when populating it) so the stored type matches what sendStatus emits and remove
the unnecessary as PartInstance[] cast; update any places that push/pop into
_partInstancesInPlaylist (e.g., assignment sites in ResolvedPlaylistTopic
methods) to produce/convert DBPartInstance -> PartInstance if needed so the
compiler enforces shape compatibility.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

Comment on lines +1 to +17
$ref: '../partBase/partBase-example.yaml'
instanceId: 'partInstance_current_1'
externalId: 'ext_part_1'
rank: 10
invalid: false
floated: false
untimed: false
invalidReason:
$ref: '../partInvalidReason/partInvalidReason-example.yaml'
state: current
createdByAdLib: false
publicData:
partType: 'intro'
timing:
$ref: '../../timing/resolvedPartTiming/resolvedPartTiming-example.yaml'
pieces:
- $ref: '../../piece/resolvedPiece/resolvedPiece-example.yaml'
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
fd -t f 'package.json' packages/live-status-gateway-api | head -5 | xargs -I{} cat {}
fd -t f 'generate-schema' packages/live-status-gateway-api

Repository: Sofie-Automation/sofie-core

Length of output: 2243


🏁 Script executed:

cat packages/live-status-gateway-api/scripts/generate-schema-types.mjs

Repository: Sofie-Automation/sofie-core

Length of output: 4474


🏁 Script executed:

cat packages/live-status-gateway-api/scripts/merge-schemas.mjs

Repository: Sofie-Automation/sofie-core

Length of output: 1156


🏁 Script executed:

fd -t f 'schema.ts' packages/live-status-gateway-api/src | head -1 | xargs wc -l
fd -t f 'asyncapi.yaml' packages/live-status-gateway-api/src | xargs ls -lh

Repository: Sofie-Automation/sofie-core

Length of output: 242


🏁 Script executed:

grep -A 30 'ResolvedPart:' packages/live-status-gateway-api/src/generated/asyncapi.yaml | head -40

Repository: Sofie-Automation/sofie-core

Length of output: 53


🏁 Script executed:

rg -i 'resolvedpart' packages/live-status-gateway-api/src/generated/asyncapi.yaml -A 5 | head -50

Repository: Sofie-Automation/sofie-core

Length of output: 1668


🏁 Script executed:

cat packages/live-status-gateway-api/api/components/part/resolvedPart/resolvedPart.yaml

Repository: Sofie-Automation/sofie-core

Length of output: 2133


🏁 Script executed:

cat packages/live-status-gateway-api/api/components/part/resolvedPart/resolvedPart-example.yaml

Repository: Sofie-Automation/sofie-core

Length of output: 529


🏁 Script executed:

rg -A 20 'resolvedPart-example' packages/live-status-gateway-api/src/generated/asyncapi.yaml | head -40

Repository: Sofie-Automation/sofie-core

Length of output: 53


🏁 Script executed:

npm info `@apidevtools/json-schema-ref-parser` versions --json | head -20

Repository: Sofie-Automation/sofie-core

Length of output: 286


🏁 Script executed:

fd -t f 'README' packages/live-status-gateway-api | xargs cat

Repository: Sofie-Automation/sofie-core

Length of output: 771


🏁 Script executed:

rg -B 5 -A 50 'examples:' packages/live-status-gateway-api/src/generated/asyncapi.yaml | grep -A 50 'ResolvedPart' | head -70

Repository: Sofie-Automation/sofie-core

Length of output: 4977


🏁 Script executed:

cat > /tmp/check_ref_behavior.js << 'EOF'
// Check json-schema-ref-parser behavior with $ref and siblings
const content = `
$ref: '../partBase/partBase-example.yaml'
instanceId: 'partInstance_current_1'
externalId: 'ext_part_1'
rank: 10
`;

console.log("According to JSON Schema spec (section 8.2.4):");
console.log("When processing a $ref keyword, sibling keywords should be ignored in strict compliance.");
console.log("\nHowever, `@apidevtools/json-schema-ref-parser.dereference`() is a convenience tool");
console.log("that MERGES sibling keywords with the referenced content (non-standard behavior).");
console.log("\nThis creates portability issues with strict validators and other tooling.");
EOF
node /tmp/check_ref_behavior.js

Repository: Sofie-Automation/sofie-core

Length of output: 445


🏁 Script executed:

rg -t yaml 'allOf' packages/live-status-gateway-api/api/components/part/resolvedPart/ -B 5 -A 10

Repository: Sofie-Automation/sofie-core

Length of output: 1860


Fix the example file to match the schema's composition pattern.

The schema definition in resolvedPart.yaml correctly uses allOf to compose partBase with sibling properties. However, the example file resolvedPart-example.yaml uses a $ref at the root with sibling keys, which violates strict JSON Schema semantics—though your current tooling (@apidevtools/json-schema-ref-parser) happens to merge them.

For portability and spec compliance, align the example with the schema's allOf pattern by either inlining the base example fields or using allOf syntax in the example as well.

🤖 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/part/resolvedPart/resolvedPart-example.yaml`
around lines 1 - 17, The example file resolvedPart-example.yaml currently uses a
root-level $ref to partBase-example.yaml with sibling properties which violates
JSON Schema composition semantics; update the example to match the schema's
allOf composition by replacing the root $ref with an allOf array that includes a
$ref to '../partBase/partBase-example.yaml' and a second object containing the
sibling properties (instanceId, externalId, rank, invalid, floated, untimed,
invalidReason ref, state, createdByAdLib, publicData, timing ref, pieces refs),
or alternatively inline all fields from partBase into this file so there is no
root $ref with siblings; ensure references to
'../partInvalidReason/partInvalidReason-example.yaml',
'../../timing/resolvedPartTiming/resolvedPartTiming-example.yaml', and
'../../piece/resolvedPiece/resolvedPiece-example.yaml' remain intact.

Comment on lines +1 to +3
paused: false
zeroTime: 1706371920000
pauseTime: 1706371900000
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

pauseTime set while paused: false is inconsistent for the “running” variant.

The sibling example tTimerStatus-countdownRunning-example.yaml uses pauseTime: null when paused: false in its projected block, which matches typical timer semantics (a running timer has no active pause timestamp). Here pauseTime: 1706371900000 alongside paused: false conflates the running and paused cases and may mislead consumers reading this as the canonical “running” example.

✏️ Proposed fix
 paused: false
 zeroTime: 1706371920000
-pauseTime: 1706371900000
+pauseTime: null
📝 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
paused: false
zeroTime: 1706371920000
pauseTime: 1706371900000
paused: false
zeroTime: 1706371920000
pauseTime: 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/timerStateRunning-example.yaml`
around lines 1 - 3, The example for the "running" timer has an inconsistent
state: the fields paused and pauseTime conflict (paused: false but pauseTime:
1706371900000). Update the timerStateRunning example so that when paused is
false the pauseTime is null (or remove the pauseTime field) to match the
countdownRunning example and represent an active/running timer; change the
pauseTime value in timerStateRunning-example.yaml accordingly.

Comment on lines +77 to +91
if (this._currentPlaylist && this._rundownIds.length && this._activationId) {
const sameSubscription =
areElementsShallowEqual(this._rundownIds, prevRundownIds) && prevActivationId === this._activationId
if (!sameSubscription) {
this.stopSubscription()
this.setupSubscription(this._rundownIds, this._activationId)
} else if (this._subscriptionId) {
this.updateAndNotify()
} else {
this.clearAndNotify()
}
} else {
this.clearAndNotify()
}
}
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 | 🟠 Major

Subscription leak when playlist deactivates or rundownIdsInOrder becomes empty.

The else branch at Line 88 calls clearAndNotify() but never stopSubscription(). If we were previously subscribed (activation id present, rundowns populated) and the playlist later deactivates or is cleared, the server-side observer stays alive indefinitely — still receiving updates for the old scope. Over time this accumulates stale observers and also causes onDocumentEventchanged()updateCollectionData() to overwrite all with whatever the previous collection still contains.

Compare with PieceInstancesInPlaylistHandler.resubscribe which explicitly calls stopSubscription() in both guard branches.

Proposed fix
 		} else {
+			this.stopSubscription()
 			this.clearAndNotify()
 		}
📝 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
if (this._currentPlaylist && this._rundownIds.length && this._activationId) {
const sameSubscription =
areElementsShallowEqual(this._rundownIds, prevRundownIds) && prevActivationId === this._activationId
if (!sameSubscription) {
this.stopSubscription()
this.setupSubscription(this._rundownIds, this._activationId)
} else if (this._subscriptionId) {
this.updateAndNotify()
} else {
this.clearAndNotify()
}
} else {
this.clearAndNotify()
}
}
if (this._currentPlaylist && this._rundownIds.length && this._activationId) {
const sameSubscription =
areElementsShallowEqual(this._rundownIds, prevRundownIds) && prevActivationId === this._activationId
if (!sameSubscription) {
this.stopSubscription()
this.setupSubscription(this._rundownIds, this._activationId)
} else if (this._subscriptionId) {
this.updateAndNotify()
} else {
this.clearAndNotify()
}
} else {
this.stopSubscription()
this.clearAndNotify()
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts`
around lines 77 - 91, When the playlist deactivates or _rundownIds becomes empty
the code calls clearAndNotify() but never stops the server observer; modify the
else branch (the branch where !(this._currentPlaylist && this._rundownIds.length
&& this._activationId)) to call stopSubscription() before clearAndNotify() (or
at least when there was an active subscription previously, e.g., when
prevActivationId and prevRundownIds exist or this._subscriptionId is set) so
that setupSubscription/stopSubscription usage matches the logic in
PieceInstancesInPlaylistHandler.resubscribe and avoids leaking observers.

Comment on lines +15 to +17
return {
id: unprotectString(basePiece._id),
instanceId: unprotectString(instance?._id),
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

unprotectString(instance?._id) can receive undefined.

If instance is missing, instance?._id is undefined and unprotectString is typically typed to accept a protected-string, producing the literal "undefined" as instanceId. Guard this the same way the id field is handled elsewhere.

🛡️ Suggested fix
-		instanceId: unprotectString(instance?._id),
+		instanceId: instance?._id ? unprotectString(instance._id) : '',
📝 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
return {
id: unprotectString(basePiece._id),
instanceId: unprotectString(instance?._id),
return {
id: unprotectString(basePiece._id),
instanceId: instance?._id ? unprotectString(instance._id) : '',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/pieces/toResolvedPieceStatus.ts`
around lines 15 - 17, The instanceId currently calls
unprotectString(instance?._id) which can receive undefined and produce the
literal "undefined"; update the returned instanceId so it only calls
unprotectString when instance exists (mirror how id is handled): check instance
(or instance._id) and return unprotectString(instance._id) when present,
otherwise return undefined (or null if the surrounding code expects that).
Modify the return in toResolvedPieceStatus (the instanceId field) accordingly.

export function toResolvedRundownStatus(ctx: ResolvedPlaylistConversionContext, rundownId: string): ResolvedRundown {
const rundown = ctx.rundownsById.get(String(rundownId))
const orderedSegments = getOrderedSegmentsInRundown(ctx, rundownId)
const rank = Math.max(0, ctx.orderedRundownIds.indexOf(String(rundownId)))
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

rank silently collapses to 0 when the rundown is not in orderedRundownIds.

If rundownId is absent from ctx.orderedRundownIds, indexOf returns -1 and the Math.max(0, …) clamp produces rank: 0, making this indistinguishable from the legitimate first rundown. Since the "missing rundown" case already returns segments: [] with empty defaulted fields, you may want to handle the -1 case explicitly (e.g. fall back to a sentinel like the current orderedRundownIds.length, or let the caller ensure only known ids are passed). Low severity as this likely doesn't occur under normal control flow.

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

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/rundowns/toResolvedRundownStatus.ts`
at line 9, The code currently computes rank with Math.max(0,
ctx.orderedRundownIds.indexOf(String(rundownId))) which collapses a missing
rundown (index -1) to 0; change this to explicitly handle the -1 case in
toResolvedRundownStatus (or the enclosing function) by checking const idx =
ctx.orderedRundownIds.indexOf(String(rundownId)); if (idx === -1) set rank to a
sentinel (e.g. ctx.orderedRundownIds.length) or undefined and propagate that
value (or return early) so missing rundowns are distinguishable from the first
rundown; update any downstream logic that reads rank accordingly.

Comment on lines +15 to +25
export function serializePart(partExtended: unknown): any {
const part = partExtended as any
return {
partId: part.partId,
instance: part.instance,
renderedDuration: part.renderedDuration,
startsAt: part.startsAt,
willProbablyAutoNext: part.willProbablyAutoNext,
pieces: part.pieces?.map(serializePiece) ?? [],
}
}
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

Inconsistent null handling between serializePart and serializePiece.

serializePiece guards with piece ?? {} (Line 6), but serializePart dereferences part.partId directly. If a falsy partExtended is ever passed in, this will throw, which is inconsistent with both sibling functions in this module. Consider mirroring the guard for consistency:

Proposed fix
 export function serializePart(partExtended: unknown): any {
-	const part = partExtended as any
+	const part = (partExtended as any) ?? {}
 	return {
 		partId: part.partId,
📝 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
export function serializePart(partExtended: unknown): any {
const part = partExtended as any
return {
partId: part.partId,
instance: part.instance,
renderedDuration: part.renderedDuration,
startsAt: part.startsAt,
willProbablyAutoNext: part.willProbablyAutoNext,
pieces: part.pieces?.map(serializePiece) ?? [],
}
}
export function serializePart(partExtended: unknown): any {
const part = (partExtended as any) ?? {}
return {
partId: part.partId,
instance: part.instance,
renderedDuration: part.renderedDuration,
startsAt: part.startsAt,
willProbablyAutoNext: part.willProbablyAutoNext,
pieces: part.pieces?.map(serializePiece) ?? [],
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/segments/serializeResolvedSegment.ts`
around lines 15 - 25, serializePart dereferences partExtended directly which can
throw if a falsy value is passed; mirror serializePiece's guard by defaulting
partExtended to an empty object before accessing fields (e.g., treat part =
partExtended ?? {}), then use optional chaining (part.partId, part.instance,
etc.) and keep the pieces mapping as is to ensure consistent null-safe behavior
with serializePiece and other serializers.

private _rundowns: DBRundown[] = []
private _segments: DBSegment[] = []
private _parts: DBPart[] = []
private _partInstancesInPlaylist: DBPartInstance[] = []
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
# Compare DBPartInstance and PartInstance to see what fields differ.
rg -nP --type=ts -C2 '^\s*(export\s+)?(interface|class|type)\s+(DBPartInstance|PartInstance)\b'
# Also inspect what toResolvedPlaylistStatus / conversionContext actually consume from PartInstance.
fd -t f 'conversionContext.ts' --exec rg -nP -C2 'PartInstance' {}

Repository: Sofie-Automation/sofie-core

Length of output: 3153


🏁 Script executed:

cat -n packages/live-status-gateway/src/topics/resolvedPlaylistTopic.ts | head -80

Repository: Sofie-Automation/sofie-core

Length of output: 4328


🏁 Script executed:

rg -nA 10 'export.*function toResolvedPlaylistStatus' packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/events/toResolvedPlaylistStatus.ts | head -30

Repository: Sofie-Automation/sofie-core

Length of output: 446


🏁 Script executed:

cat -n packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/events/toResolvedPlaylistStatus.ts | head -60

Repository: Sofie-Automation/sofie-core

Length of output: 2517


🏁 Script executed:

cat -n packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts | head -150

Repository: Sofie-Automation/sofie-core

Length of output: 7116


🏁 Script executed:

cat -n packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts | tail -n +150 | head -50

Repository: Sofie-Automation/sofie-core

Length of output: 1760


🏁 Script executed:

rg -rn 'isTemporary' packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/

Repository: Sofie-Automation/sofie-core

Length of output: 53


🏁 Script executed:

rg -rn 'isTemporary' packages/live-status-gateway/src/topics/ packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts

Repository: Sofie-Automation/sofie-core

Length of output: 53


🏁 Script executed:

cat -n packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts | head -80

Repository: Sofie-Automation/sofie-core

Length of output: 3732


🏁 Script executed:

rg -rn 'isTemporary' packages/live-status-gateway/

Repository: Sofie-Automation/sofie-core

Length of output: 53


🏁 Script executed:

rg -rn 'partInstancesInPlaylistState' packages/live-status-gateway/src/

Repository: Sofie-Automation/sofie-core

Length of output: 3637


🏁 Script executed:

cat -n packages/live-status-gateway/src/collections/partInstancesInPlaylistHandler.ts | tail -n +50 | head -80

Repository: Sofie-Automation/sofie-core

Length of output: 2051


🏁 Script executed:

rg -rn 'makePartInstance' packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/ -A 5 | head -40

Repository: Sofie-Automation/sofie-core

Length of output: 5093


🏁 Script executed:

rg -n 'function.*makePartInstance|export.*function.*n\s*\(' packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/resolvedPlaylistConversionTestUtils.ts -A 10

Repository: Sofie-Automation/sofie-core

Length of output: 784


🏁 Script executed:

cat -n packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/resolvedPlaylistConversionTestUtils.ts | sed -n '73,100p'

Repository: Sofie-Automation/sofie-core

Length of output: 930


🏁 Script executed:

rg -rn 'isTemporary' packages/corelib/src/dataModel/PartInstance.ts

Repository: Sofie-Automation/sofie-core

Length of output: 82


🏁 Script executed:

cat -n packages/corelib/src/dataModel/PartInstance.ts | head -50

Repository: Sofie-Automation/sofie-core

Length of output: 2370


Remove the unsafe type cast of DBPartInstance[] to PartInstance[].

_partInstancesInPlaylist stores DBPartInstance objects (line 45), but at line 71 it is cast to PartInstance[]. Since PartInstance extends DBPartInstance by adding only isTemporary: boolean, the cast creates a type mismatch: the runtime data lacks the isTemporary property that the type declares. While current conversion functions don't access this field, the cast masks the type gap and could lead to runtime errors if downstream code is later modified to rely on isTemporary.

Either store and pass the correct type (PartInstance[]), or remove the cast and update type signatures to accept DBPartInstance[].

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

In `@packages/live-status-gateway/src/topics/resolvedPlaylistTopic.ts` at line 45,
_field _partInstancesInPlaylist is typed as DBPartInstance[] but later cast to
PartInstance[] (which adds isTemporary), creating an unsafe mismatch; fix by
either changing the storage to real PartInstance objects or stop the cast and
update consumers to accept DBPartInstance[]. Concretely, update the declaration
of _partInstancesInPlaylist to PartInstance[] and ensure wherever items are
inserted into _partInstancesInPlaylist (and any factory/mapper functions) they
are given an explicit isTemporary boolean, or alternatively remove the cast to
PartInstance[] where used and change function/method signatures that consume it
to accept DBPartInstance[] (referencing _partInstancesInPlaylist,
DBPartInstance, PartInstance, and the place where the cast currently occurs) so
types align without unsafe casts.

Comment on lines +64 to +77
sendStatus(subscribers: Iterable<WebSocket>): void {
const message = toResolvedPlaylistStatus({
playlistState: this._playlist as ToResolvedPlaylistStatusProps['playlistState'],
rundownsState: this._rundowns,
showStyleBaseExtState: this._showStyleBaseExt,
segmentsState: this._segments,
partsState: this._parts,
partInstancesInPlaylistState: this._partInstancesInPlaylist as PartInstance[],
piecesInPlaylistState: this._piecesInPlaylist,
pieceInstancesInPlaylistState: this._pieceInstancesInPlaylist,
})

this.sendMessage(subscribers, message)
}
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 | 🟠 Major

Guard sendStatus against undefined required state.

sendStatus unconditionally forwards _playlist and _showStyleBaseExt to toResolvedPlaylistStatus, but createResolvedPlaylistConversionContext throws Missing playlist or showStyleBaseExt when either is undefined (see conversionContext.spec.ts). Because each handler callback triggers throttledSendStatusToAll(), the first updates arriving before playlist/showStyleBase are loaded (or after deactivation) will cause the throttled send to throw inside the underscore throttle wrapper, swallowing the next scheduled send and producing confusing error logs.

Add an early return while required inputs are missing, similar to how other topics guard their required state:

🛡️ Suggested guard
 	sendStatus(subscribers: Iterable<WebSocket>): void {
+		if (!this._playlist || !this._showStyleBaseExt) {
+			return
+		}
 		const message = toResolvedPlaylistStatus({
-			playlistState: this._playlist as ToResolvedPlaylistStatusProps['playlistState'],
+			playlistState: this._playlist,
 			rundownsState: this._rundowns,
 			showStyleBaseExtState: this._showStyleBaseExt,
 			segmentsState: this._segments,
 			partsState: this._parts,
 			partInstancesInPlaylistState: this._partInstancesInPlaylist as PartInstance[],
 			piecesInPlaylistState: this._piecesInPlaylist,
 			pieceInstancesInPlaylistState: this._pieceInstancesInPlaylist,
 		})

 		this.sendMessage(subscribers, message)
 	}
📝 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
sendStatus(subscribers: Iterable<WebSocket>): void {
const message = toResolvedPlaylistStatus({
playlistState: this._playlist as ToResolvedPlaylistStatusProps['playlistState'],
rundownsState: this._rundowns,
showStyleBaseExtState: this._showStyleBaseExt,
segmentsState: this._segments,
partsState: this._parts,
partInstancesInPlaylistState: this._partInstancesInPlaylist as PartInstance[],
piecesInPlaylistState: this._piecesInPlaylist,
pieceInstancesInPlaylistState: this._pieceInstancesInPlaylist,
})
this.sendMessage(subscribers, message)
}
sendStatus(subscribers: Iterable<WebSocket>): void {
if (!this._playlist || !this._showStyleBaseExt) {
return
}
const message = toResolvedPlaylistStatus({
playlistState: this._playlist,
rundownsState: this._rundowns,
showStyleBaseExtState: this._showStyleBaseExt,
segmentsState: this._segments,
partsState: this._parts,
partInstancesInPlaylistState: this._partInstancesInPlaylist as PartInstance[],
piecesInPlaylistState: this._piecesInPlaylist,
pieceInstancesInPlaylistState: this._pieceInstancesInPlaylist,
})
this.sendMessage(subscribers, message)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/live-status-gateway/src/topics/resolvedPlaylistTopic.ts` around
lines 64 - 77, The sendStatus method currently calls toResolvedPlaylistStatus
with this._playlist and this._showStyleBaseExt even when they may be undefined;
add an early-return guard at the top of sendStatus that checks required state
(e.g. this._playlist and this._showStyleBaseExt) and returns immediately if
either is missing to avoid calling toResolvedPlaylistStatus (which triggers
createResolvedPlaylistConversionContext and can throw). Locate sendStatus in
resolvedPlaylistTopic.ts and mirror the existing pattern used in other topics
(early-return when required inputs are absent) so throttledSendStatusToAll won't
propagate exceptions.

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 (5)
packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/__tests__/toTTimers.spec.ts (2)

23-27: Optional: strengthen the "empty" assertion.

toMatchObject([{ index: 1 }, ...]) only validates the index field. Consider also asserting configured: false and label: '' (or snapshotting the full empty shape) so regressions to emptyTimer() are caught here rather than only in downstream consumers.

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

In
`@packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/__tests__/toTTimers.spec.ts`
around lines 23 - 27, The test for toTTimers should assert the full empty-timer
shape rather than only index; update the assertions in the 'returns 3 empty
timers for nullish/empty input' test to also check configured: false and label:
'' (or replace the three expect(...).toMatchObject(...) calls with a
toStrictEqual/toMatchObject against the complete empty timer shape produced by
emptyTimer()) so regressions to emptyTimer() are caught; reference the toTTimers
function and emptyTimer shape in the update.

3-10: Nit: mode/state/projectedState/anchorPartId typed as any forces as any at every call site.

Typing these as unknown (or the real RundownTTimer field types) and dropping the as any casts on Lines 30, 31, 33, 34, 38, 43, 48 would make the tests closer to real usage and catch shape regressions. Non-blocking.

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

In
`@packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/__tests__/toTTimers.spec.ts`
around lines 3 - 10, The TimerInput type currently types
mode/state/projectedState/anchorPartId as any; change those properties to
unknown (or the concrete RundownTTimer field types) on the TimerInput type and
then remove the unnecessary as any casts at the test call sites (the places
where TimerInput objects are passed into the toTTimers tests) so the tests use
stronger typing and will catch shape regressions; update any assertions or
helper usages accordingly to satisfy the new types (e.g., narrow or cast only
where truly needed).
packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/toTTimers.ts (1)

17-28: Minor: mode/state are passed through even when configured is false.

If only one of mode/state is set on the input, configured is false but the non-null side is still surfaced on TTimerStatus. The schema describes these fields as "null if not configured", so consumers may reasonably expect both to be null together. Consider normalizing both to null when configured is false (or asserting the invariant upstream).

♻️ Optional tweak
-	const { index, label, mode, state, projectedState, anchorPartId } = timer
-	return {
-		index: index as TTimerIndex,
-		label,
-		configured: !!(mode && state),
-		mode: mode as TTimerStatus['mode'],
-		state: state as TTimerStatus['state'],
+	const { index, label, mode, state, projectedState, anchorPartId } = timer
+	const configured = !!(mode && state)
+	return {
+		index: index as TTimerIndex,
+		label,
+		configured,
+		mode: configured ? (mode as TTimerStatus['mode']) : null,
+		state: configured ? (state as TTimerStatus['state']) : null,
 		projected: projectedState ? (projectedState as NonNullable<TTimerStatus['projected']>) : null,
 		anchorPartId: anchorPartId ? unprotectString(anchorPartId) : null,
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/toTTimers.ts`
around lines 17 - 28, The toTTimer function currently sets TTimerStatus.mode and
.state even when configured is false, violating the schema that both should be
null if not configured; update toTTimer (which accepts RundownTTimer and returns
TTimerStatus) to compute configured = !!(mode && state) and then set mode and
state to configured ? (mode as TTimerStatus['mode']) / (state as
TTimerStatus['state']) : null so both are normalized to null when configured is
false (leave projected and anchorPartId handling unchanged).
packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts (2)

189-192: reset exclusion applied — consider tightening the cast.

The reset: { $ne: true } merge now aligns with the getActivePartInstances contract. Two small notes:

  • ...(selector as any) discards type information; selector as Record<string, unknown> (or a narrower MongoQuery<PartInstance> type) would preserve safety.
  • If a caller ever supplies its own reset field in the selector it would be silently overwritten; unlikely in current callers but worth a brief comment to document the precedence.
♻️ Suggestion
-		getActivePartInstances: (_playlistPick: Pick<DBRundownPlaylist, '_id'>, selector?: unknown) => {
-			const mergedSelector = selector ? { ...(selector as any), reset: { $ne: true } } : { reset: { $ne: true } }
-			return mongoWhereFilter(partInstancesInPlaylistState, mergedSelector as never)
-		},
+		getActivePartInstances: (_playlistPick: Pick<DBRundownPlaylist, '_id'>, selector?: unknown) => {
+			// Always exclude reset instances; any caller-supplied `reset` is intentionally overridden.
+			const mergedSelector = {
+				...((selector as Record<string, unknown> | undefined) ?? {}),
+				reset: { $ne: true },
+			}
+			return mongoWhereFilter(partInstancesInPlaylistState, mergedSelector as never)
+		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts`
around lines 189 - 192, In getActivePartInstances, tighten the unsafe cast and
document precedence: change the selector cast from "any" to a safer type such as
Record<string, unknown> (or the specific MongoQuery<PartInstance> type used in
your codebase) when spreading into mergedSelector, and add a brief inline
comment noting that the explicit reset: { $ne: true } will override any reset
provided by the caller; keep the same mergedSelector construction and return
mongoWhereFilter(partInstancesInPlaylistState, mergedSelector as never).

52-54: Optional: split the missing-input error for easier diagnosis.

A single combined message hides which input was missing, which can slow down triage when one handler stops publishing. Consider separate throws or include names in the message.

♻️ Suggestion
-	if (!playlist || !showStyleBaseExt) {
-		throw new Error('Missing playlist or showStyleBaseExt')
-	}
+	if (!playlist) throw new Error('createResolvedPlaylistConversionContext: missing playlistState')
+	if (!showStyleBaseExt) throw new Error('createResolvedPlaylistConversionContext: missing showStyleBaseExtState')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts`
around lines 52 - 54, The current guard in conversionContext.ts throws a
combined error ('Missing playlist or showStyleBaseExt') which obscures which
input is absent; update the check around playlist and showStyleBaseExt in the
conversion context to throw separate, explicit errors (or a single error that
names the missing variable) so handlers can quickly identify the missing
input—e.g., check playlist and showStyleBaseExt individually and throw 'Missing
playlist' or 'Missing showStyleBaseExt' (referencing the playlist and
showStyleBaseExt symbols in conversionContext.ts).
🤖 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/helpers/resolvedPlaylistConversion/context/conversionContext.ts`:
- Around line 169-177: The segmentsFindOne implementation assumes selector._id
is a plain id and passes objects to unprotectString, causing failures for
MongoQuery forms; update segmentsFindOne to use the existing mongoWhereFilter
(already imported) to evaluate the selector against each segment in
segmentsState (falling back to comparing unprotected id when selector is a raw
id/primitive) so it correctly supports operator queries like { _id: { $in: [...]
} } and { _id: { $eq: ... } } and returns the first matching DBSegment.

---

Nitpick comments:
In
`@packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/__tests__/toTTimers.spec.ts`:
- Around line 23-27: The test for toTTimers should assert the full empty-timer
shape rather than only index; update the assertions in the 'returns 3 empty
timers for nullish/empty input' test to also check configured: false and label:
'' (or replace the three expect(...).toMatchObject(...) calls with a
toStrictEqual/toMatchObject against the complete empty timer shape produced by
emptyTimer()) so regressions to emptyTimer() are caught; reference the toTTimers
function and emptyTimer shape in the update.
- Around line 3-10: The TimerInput type currently types
mode/state/projectedState/anchorPartId as any; change those properties to
unknown (or the concrete RundownTTimer field types) on the TimerInput type and
then remove the unnecessary as any casts at the test call sites (the places
where TimerInput objects are passed into the toTTimers tests) so the tests use
stronger typing and will catch shape regressions; update any assertions or
helper usages accordingly to satisfy the new types (e.g., narrow or cast only
where truly needed).

In
`@packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/toTTimers.ts`:
- Around line 17-28: The toTTimer function currently sets TTimerStatus.mode and
.state even when configured is false, violating the schema that both should be
null if not configured; update toTTimer (which accepts RundownTTimer and returns
TTimerStatus) to compute configured = !!(mode && state) and then set mode and
state to configured ? (mode as TTimerStatus['mode']) / (state as
TTimerStatus['state']) : null so both are normalized to null when configured is
false (leave projected and anchorPartId handling unchanged).

In
`@packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts`:
- Around line 189-192: In getActivePartInstances, tighten the unsafe cast and
document precedence: change the selector cast from "any" to a safer type such as
Record<string, unknown> (or the specific MongoQuery<PartInstance> type used in
your codebase) when spreading into mergedSelector, and add a brief inline
comment noting that the explicit reset: { $ne: true } will override any reset
provided by the caller; keep the same mergedSelector construction and return
mongoWhereFilter(partInstancesInPlaylistState, mergedSelector as never).
- Around line 52-54: The current guard in conversionContext.ts throws a combined
error ('Missing playlist or showStyleBaseExt') which obscures which input is
absent; update the check around playlist and showStyleBaseExt in the conversion
context to throw separate, explicit errors (or a single error that names the
missing variable) so handlers can quickly identify the missing input—e.g., check
playlist and showStyleBaseExt individually and throw 'Missing playlist' or
'Missing showStyleBaseExt' (referencing the playlist and showStyleBaseExt
symbols in conversionContext.ts).
🪄 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: 0fae5cc2-34e3-488f-aed7-849813b5a1f2

📥 Commits

Reviewing files that changed from the base of the PR and between 8253eeb and e8db066.

⛔ 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 (8)
  • packages/live-status-gateway-api/api/components/rundown/resolvedRundown/resolvedRundown.yaml
  • packages/live-status-gateway/src/collections/piecesInPlaylistHandler.ts
  • packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/__tests__/toTTimers.spec.ts
  • packages/live-status-gateway/src/topics/helpers/activePlaylistConversion/timers/toTTimers.ts
  • packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/conversionContext.spec.ts
  • packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/__tests__/resolvedPlaylistConversionTestUtils.ts
  • packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/context/conversionContext.ts
  • packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/events/toResolvedPlaylistStatus.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/live-status-gateway-api/api/components/rundown/resolvedRundown/resolvedRundown.yaml
  • packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/tests/conversionContext.spec.ts
  • packages/live-status-gateway/src/collections/piecesInPlaylistHandler.ts
  • packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/tests/resolvedPlaylistConversionTestUtils.ts
  • packages/live-status-gateway/src/topics/helpers/resolvedPlaylistConversion/events/toResolvedPlaylistStatus.ts

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) Contribution from CBC/Radio-Canada Contributions sponsored by CBC/Radio-Canada (cbc.radio-canada.ca)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants