Skip to content

feat: TSR Device Feedback#456

Open
Julusian wants to merge 14 commits into
mainfrom
feat/tsr-device-feedback
Open

feat: TSR Device Feedback#456
Julusian wants to merge 14 commits into
mainfrom
feat/tsr-device-feedback

Conversation

@Julusian
Copy link
Copy Markdown
Member

About the Contributor

This pull request is posted on behalf of the BBC

Type of Contribution

This is a: Feature

New Behavior

RFC: Sofie-Automation/sofie-core#1670

Allow TSR devices to report events when external state changes occur. In sofie, this will trigger a new blueprints method to allow for custom handling of these state changes.

Each device type has a list of defined events it can produce (with typings that are available to consumers of the api/events). The user of TSR has to tell each device which events to emit, any other events are discarded inside of the device thread.

Some types generation has been adjusted a bit to be able to propagate the new types as needed.

Testing Instructions

This has been tested with both new logging added to quick-tsr, and the full flow into sofie blueprints.

Other Information

Status

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1d3abb01-1778-4a84-85fc-10fb7fbbdeac

📥 Commits

Reviewing files that changed from the base of the PR and between cd848d3 and 4079605.

📒 Files selected for processing (6)
  • packages/timeline-state-resolver-api/src/device.ts
  • packages/timeline-state-resolver-types/src/events.ts
  • packages/timeline-state-resolver/src/integrations/atem/index.ts
  • packages/timeline-state-resolver/src/service/DeviceInstance.ts
  • packages/timeline-state-resolver/src/service/__tests__/stateEventHandler.spec.ts
  • packages/timeline-state-resolver/src/service/stateEventHandler.ts

Walkthrough

Adds a typed device state-event system: new event types, StateEventHandler for subscription-filtered batching, context API event reporting, device and connection wiring for subscriptions/emission, many integration constructor type updates, ATEM external-address event reporting, and updated schema generation for per-integration Events types.

Changes

Cohort / File(s) Summary
Event Type System
packages/timeline-state-resolver-types/src/events.ts, packages/timeline-state-resolver-types/src/integrations/atem/events.ts
New TS types: TSREventTypesMap, TSRStateEvent, SomeTSRStateEvent; ATEM-specific AtemEvents payload type.
Core Type Exports & Imports
packages/timeline-state-resolver-types/src/index.ts, packages/timeline-state-resolver-types/src/expectedPlayoutItems.ts
Exported TSRMappingOptions, export ./events.js, updated integration re-exports to */timeline.js, fixed VIZMSE import path.
Schema Generation
packages/timeline-state-resolver-tools/bin/schema-types.mjs
Generator now imports per-integration Events and emits TSRDeviceTypesMap when applicable; integrates Events into generated {DirId}DeviceTypes.
API Type Changes
packages/timeline-state-resolver-api/src/device.ts, packages/timeline-state-resolver-api/src/tsr-api.ts
DeviceTypes may include Events; DeviceContextAPI gains reportStateEvent; BaseDeviceAPI adds onAddressExternallyChanged/onAddressControlRestored; DeviceEntry.deviceClass context generic updated.
StateEventHandler & Tests
packages/timeline-state-resolver/src/service/stateEventHandler.ts, packages/timeline-state-resolver/src/service/__tests__/stateEventHandler.spec.ts
New class managing allowlist subscriptions, batching (setImmediate), and flush callback; comprehensive Jest tests for filtering, batching, and payloads.
DeviceInstance & Device API Wiring
packages/timeline-state-resolver/src/service/DeviceInstance.ts, packages/timeline-state-resolver/src/devices/device.ts
DeviceInstanceWrapper wired with StateEventHandler, emits stateEvent, exposes reportStateEvent and setEventSubscriptions; base Device adds setEventSubscriptions stub; hooks call onAddressExternallyChanged/onAddressControlRestored.
Connection & Remote APIs
packages/timeline-state-resolver/src/service/ConnectionManager.ts, packages/timeline-state-resolver/src/service/remoteDeviceInstance.ts, packages/timeline-state-resolver/src/conductor.ts
ConnectionManager emits forwarded connectionEvent:stateEvent and adds setDeviceEventSubscriptions; remote integration and Conductor expose setEventSubscriptions/setDeviceEventSubscriptions.
Quick-TSR Integration
packages/quick-tsr/src/index.ts, packages/quick-tsr/src/tsrHandler.ts, packages/quick-tsr/input/settings.ts
TSRSettings gains optional stateEvents typing; handler optionally wires subscription initialization and logs incoming state events; commented scaffolding in settings file.
Integration Constructor Type Updates
packages/timeline-state-resolver/src/integrations/.../index.ts (many integrations) and packages/timeline-state-resolver/src/integrations/.../kairos/*
Updated constructors to DeviceContextAPI<DeviceTypes, DeviceState,...> across integrations (type-only changes). ATEM integration adds external-address handling to report me.<n>.inputs.
Import Path Fixes in Integrations
packages/timeline-state-resolver-types/src/integrations/.../timeline.ts (multiple files)
Fixed relative imports for DeviceType/shared types from ../generated/index.js/../index.js to ../../generated/index.js/../../index.js.
Tests & Mocks Updated
packages/timeline-state-resolver/src/__tests__/mockDeviceInstanceWrapper.ts, packages/timeline-state-resolver/src/integrations/__tests__/testlib.ts, packages/timeline-state-resolver/src/integrations/sisyfos/__tests__/sisyfos.spec.ts
Mocks updated for new DeviceContextAPI generic signatures; mock setEventSubscriptions added to MockDeviceInstanceWrapper.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Conductor
    participant ConnMgr as ConnectionManager
    participant DeviceInst as DeviceInstanceWrapper
    participant StateHandler as StateEventHandler
    participant Listener

    Client->>Conductor: setDeviceEventSubscriptions(deviceId, events[])
    Conductor->>ConnMgr: setDeviceEventSubscriptions(deviceId, events[])
    ConnMgr->>DeviceInst: setEventSubscriptions(events[])
    DeviceInst->>StateHandler: setEventSubscriptions(events[])

    Note over DeviceInst,StateHandler: Later, device reports an external change
    DeviceInst->>StateHandler: report(eventName, payload)
    StateHandler->>StateHandler: filter by allowed set, queue, schedule flush (setImmediate)

    Note over StateHandler: next tick
    StateHandler->>DeviceInst: onFlush([SomeTSRStateEvent])
    DeviceInst->>ConnMgr: emit('connectionEvent:stateEvent', events)
    ConnMgr->>Listener: connectionEvent:stateEvent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jstarpl
  • nytamin
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: TSR Device Feedback' directly corresponds to the main feature described in the PR: enabling TSR devices to report events when external state changes occur.
Description check ✅ Passed The description is well-related to the changeset, providing context about the feature (RFC link, new behavior), testing approach, and contributor information.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tsr-device-feedback

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@Julusian Julusian added the contribution from BBC Contributions sponsored by BBC (bbc.co.uk) label Apr 27, 2026
@Julusian Julusian marked this pull request as ready for review April 27, 2026 14:25
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: 5

🧹 Nitpick comments (1)
packages/quick-tsr/src/tsrHandler.ts (1)

152-156: Use defensive payload serialization in event logging.

Direct JSON.stringify on external payloads can throw and interrupt event handling. Consider a safe fallback here.

♻️ Suggested hardening
 			this.tsr.connectionManager.on('connectionEvent:stateEvent', (deviceId: string, events: SomeTSRStateEvent[]) => {
 				for (const e of events) {
-					const payloadStr = e.payload === null ? 'null' : JSON.stringify(e.payload)
+					let payloadStr: string
+					try {
+						payloadStr = e.payload === null ? 'null' : JSON.stringify(e.payload)
+					} catch {
+						payloadStr = '[unserializable payload]'
+					}
 					console.log(`State event [${deviceId}] ${e.event}: ${payloadStr}`)
 				}
 			})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quick-tsr/src/tsrHandler.ts` around lines 152 - 156, The event
logging in the connectionManager handler (the callback registered on
'connectionEvent:stateEvent' within tsrHandler.ts) uses
JSON.stringify(e.payload) which can throw on circular or unsafe payloads; wrap
payload serialization in a defensive helper or inline try/catch: attempt
JSON.stringify(e.payload) and on failure fall back to a safe representation
(e.g. util.inspect, a safeStringify helper, or a fixed placeholder like
'<unserializable payload>') before calling console.log so that a bad payload
cannot break the for-loop or event processing.
🤖 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/timeline-state-resolver-tools/bin/schema-types.mjs`:
- Around line 483-485: The deviceTypesMapEntries generation uses raw dirs[i]
which can become misaligned with deviceTypeEnum when some dirs are skipped; fix
by pairing each device type with its corresponding processed dir when building
deviceTypeEnum (or create a processedDirs array or map keyed by typeId) and then
use that paired dir value instead of dirs[i] inside deviceTypesMapEntries;
update the code that constructs deviceTypeEnum (and/or introduce processedDirs)
and change deviceTypesMapEntries to reference the paired/processed dir (and
still fallback to typeId) when calling capitalise to produce the correct
*DeviceTypes name.

In `@packages/timeline-state-resolver/src/__tests__/mockDeviceInstanceWrapper.ts`:
- Around line 112-114: The mock implementation of setEventSubscriptions
currently throws a "Method not implemented." error which breaks unrelated tests;
change the jest.fn in mockDeviceInstanceWrapper so setEventSubscriptions is a
no-op by default (e.g., jest.fn((_events: string[]): void => { /* no-op */ }))
so tests that only check wiring don't fail, while keeping the mock configurable
so specific tests can still assert calls or override behavior.

In
`@packages/timeline-state-resolver/src/service/__tests__/stateEventHandler.spec.ts`:
- Around line 107-125: The test's name and comments contradict its assertion:
update the test name and inline comments in the test that uses makeHandler,
handler.report, handler.setEventSubscriptions and the onFlush assertion so they
reflect the intended behavior (that an event queued before subscriptions are
cleared is still flushed and onFlush is called once); specifically rename the
test from “does not call onFlush if all pending events were filtered” to
something like “calls onFlush for events queued before subscriptions cleared”
and adjust the explanatory comments to state that pending snapshot is taken at
flush time and therefore the queued event will be flushed.

In `@packages/timeline-state-resolver/src/service/DeviceInstance.ts`:
- Around line 146-154: The integration hook calls onAddressControlRestored and
onAddressExternallyChanged are invoked directly and can throw, so wrap each
invocation in a try/catch to prevent exceptions from bubbling out of
DeviceInstance; specifically update the call site where
this._device.onAddressControlRestored?.(a) is invoked and the handler registered
in this._stateTracker.on('deviceUpdated', (addr, ahead) => { if (ahead) {
this._device.onAddressExternallyChanged?.(addr) } }) to catch any errors from
the hook, handle or log the error and continue processing so device update
handling cannot be interrupted by a faulty integration callback.

In `@packages/timeline-state-resolver/src/service/stateEventHandler.ts`:
- Around line 24-26: setEventSubscriptions currently updates this.#allowedEvents
but queued events are not re-validated so stale ones can still be emitted; fix
by either (a) purging the internal queue of events that are no longer in the
updated this.#allowedEvents inside setEventSubscriptions, or (b) adding a
whitelist check in the flush/processQueue routine (the method that
dequeues/emits events) to skip any queued events not in this.#allowedEvents
before emitting; reference the private field `#allowedEvents`, the
setEventSubscriptions method, and the queue enqueue/flush (processQueue/flush)
logic so the filter is applied at dequeue time or when subscriptions change.

---

Nitpick comments:
In `@packages/quick-tsr/src/tsrHandler.ts`:
- Around line 152-156: The event logging in the connectionManager handler (the
callback registered on 'connectionEvent:stateEvent' within tsrHandler.ts) uses
JSON.stringify(e.payload) which can throw on circular or unsafe payloads; wrap
payload serialization in a defensive helper or inline try/catch: attempt
JSON.stringify(e.payload) and on failure fall back to a safe representation
(e.g. util.inspect, a safeStringify helper, or a fixed placeholder like
'<unserializable payload>') before calling console.log so that a bad payload
cannot break the for-loop or event processing.
🪄 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: 2b9ac143-af15-4cea-a0db-ec41639c1929

📥 Commits

Reviewing files that changed from the base of the PR and between f35c4ca and 82b989f.

⛔ Files ignored due to path filters (2)
  • packages/timeline-state-resolver-types/src/generated/atem.ts is excluded by !**/generated/**
  • packages/timeline-state-resolver-types/src/generated/index.ts is excluded by !**/generated/**
📒 Files selected for processing (73)
  • packages/quick-tsr/input/settings.ts
  • packages/quick-tsr/src/index.ts
  • packages/quick-tsr/src/tsrHandler.ts
  • packages/timeline-state-resolver-api/src/device.ts
  • packages/timeline-state-resolver-api/src/tsr-api.ts
  • packages/timeline-state-resolver-tools/bin/schema-types.mjs
  • packages/timeline-state-resolver-types/src/events.ts
  • packages/timeline-state-resolver-types/src/expectedPlayoutItems.ts
  • packages/timeline-state-resolver-types/src/index.ts
  • packages/timeline-state-resolver-types/src/integrations/abstract/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/atem/events.ts
  • packages/timeline-state-resolver-types/src/integrations/atem/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/casparcg/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/httpSend/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/hyperdeck/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/kairos/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/lawo/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/multiOsc/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/obs/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/ograf/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/osc/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/panasonicPTZ/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/pharos/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/quantel/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/shotoku/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/singularLive/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/sisyfos/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/sofieChef/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/tcpSend/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/telemetrics/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/tricaster/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/udpSend/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/viscaOverIP/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/vizMSE/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/vmix/timeline.ts
  • packages/timeline-state-resolver-types/src/integrations/websocketClient/timeline.ts
  • packages/timeline-state-resolver/src/__tests__/mockDeviceInstanceWrapper.ts
  • packages/timeline-state-resolver/src/conductor.ts
  • packages/timeline-state-resolver/src/devices/device.ts
  • packages/timeline-state-resolver/src/integrations/__tests__/testlib.ts
  • packages/timeline-state-resolver/src/integrations/abstract/index.ts
  • packages/timeline-state-resolver/src/integrations/atem/index.ts
  • packages/timeline-state-resolver/src/integrations/httpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts
  • packages/timeline-state-resolver/src/integrations/kairos/index.ts
  • packages/timeline-state-resolver/src/integrations/kairos/kairos-application-monitor.ts
  • packages/timeline-state-resolver/src/integrations/kairos/lib/kairosRamLoader.ts
  • packages/timeline-state-resolver/src/integrations/lawo/index.ts
  • packages/timeline-state-resolver/src/integrations/multiOsc/index.ts
  • packages/timeline-state-resolver/src/integrations/obs/index.ts
  • packages/timeline-state-resolver/src/integrations/ograf/index.ts
  • packages/timeline-state-resolver/src/integrations/osc/index.ts
  • packages/timeline-state-resolver/src/integrations/panasonicPTZ/index.ts
  • packages/timeline-state-resolver/src/integrations/pharos/index.ts
  • packages/timeline-state-resolver/src/integrations/quantel/index.ts
  • packages/timeline-state-resolver/src/integrations/shotoku/index.ts
  • packages/timeline-state-resolver/src/integrations/singularLive/index.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/__tests__/sisyfos.spec.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/index.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/index.ts
  • packages/timeline-state-resolver/src/integrations/tcpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/index.ts
  • packages/timeline-state-resolver/src/integrations/tricaster/index.ts
  • packages/timeline-state-resolver/src/integrations/udpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/viscaOverIP/index.ts
  • packages/timeline-state-resolver/src/integrations/vmix/index.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/index.ts
  • packages/timeline-state-resolver/src/service/ConnectionManager.ts
  • packages/timeline-state-resolver/src/service/DeviceInstance.ts
  • packages/timeline-state-resolver/src/service/__tests__/stateEventHandler.spec.ts
  • packages/timeline-state-resolver/src/service/remoteDeviceInstance.ts
  • packages/timeline-state-resolver/src/service/stateEventHandler.ts

Comment thread packages/timeline-state-resolver-tools/bin/schema-types.mjs
Comment thread packages/timeline-state-resolver/src/service/__tests__/stateEventHandler.spec.ts Outdated
Comment thread packages/timeline-state-resolver/src/service/DeviceInstance.ts Outdated
Comment thread packages/timeline-state-resolver/src/service/stateEventHandler.ts
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.

Caution

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

⚠️ Outside diff range comments (1)
packages/timeline-state-resolver/src/service/DeviceInstance.ts (1)

237-240: ⚠️ Potential issue | 🟡 Minor

terminate() does not drain/cancel the pending state-event flush.

StateEventHandler schedules a setImmediate flush on report(...) (see stateEventHandler.ts:39-46). If a device emits a state event just before terminate() is invoked (e.g. during shutdown of a sub-connection), the deferred callback will run after terminate() resolves and cause DeviceInstanceWrapper to emit 'stateEvent' for an already-terminated device. Downstream (ConnectionManagerconnectionEvent:stateEvent) this can deliver events for a device the consumer believes has been torn down.

Consider adding a dispose()/terminate() method to StateEventHandler that clears #pendingEvents and ignores the queued setImmediate, and calling it from this terminate() before/after _device.terminate().

♻️ Sketch of the change

In stateEventHandler.ts:

+	terminate(): void {
+		this.#allowedEvents.clear()
+		this.#pendingEvents = []
+		this.#terminated = true
+	}

…and guard the setImmediate callback / report against #terminated. Then in DeviceInstance.ts:

 	async terminate() {
 		this._stateHandler.terminate()
+		this._stateEventHandler.terminate()
 		return this._device.terminate()
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/service/DeviceInstance.ts` around lines
237 - 240, The terminate() on DeviceInstance currently calls
this._stateHandler.terminate() but does not cancel the StateEventHandler's
pending setImmediate flush, allowing late stateEvent emissions after tear-down;
add a disposal flag and a new dispose()/terminate() implementation on
StateEventHandler (or extend its existing terminate) that clears `#pendingEvents`,
marks it as terminated, and makes report(...) / the scheduled setImmediate
callback no-op when terminated, then update DeviceInstance.terminate() to call
the StateEventHandler disposal before (or immediately after) calling
this._device.terminate() so no queued flush can emit 'stateEvent' for a
torn-down device.
🧹 Nitpick comments (1)
packages/timeline-state-resolver/src/service/DeviceInstance.ts (1)

295-298: Add validation or diagnostic logging for event subscription mismatches.

setEventSubscriptions accepts events: string[] with no validation against the device's known events. Misconfigured or typo'd entries silently produce no emissions because report(...) filters by exact membership. While the configuration at the consumer level has type safety (TSREventTypesMap), runtime validation doesn't exist here.

Consider logging a warning (via the existing 'warning' channel or 'debug') when a subscribed event name is not something the device would report. This would make configuration errors easier to diagnose, since currently unmatched events fail silently with no feedback.

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

In `@packages/timeline-state-resolver/src/service/DeviceInstance.ts` around lines
295 - 298, setEventSubscriptions currently accepts events: string[] with no
runtime validation, so typos silently fail because report(...) only emits exact
matches; update setEventSubscriptions to validate each entry against the
device's known/reportable events (e.g., use the device definition or the same
source used by report(...) to derive allowed event names) and emit a warning via
this.emit('warning', ...) or this.emit('debug', ...) for any unknown/mismatched
event names before delegating to
this._stateEventHandler.setEventSubscriptions(events); ensure you still call
setEventSubscriptions with the original array but include the diagnostic logging
for mismatches to aid debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/timeline-state-resolver/src/service/DeviceInstance.ts`:
- Around line 237-240: The terminate() on DeviceInstance currently calls
this._stateHandler.terminate() but does not cancel the StateEventHandler's
pending setImmediate flush, allowing late stateEvent emissions after tear-down;
add a disposal flag and a new dispose()/terminate() implementation on
StateEventHandler (or extend its existing terminate) that clears `#pendingEvents`,
marks it as terminated, and makes report(...) / the scheduled setImmediate
callback no-op when terminated, then update DeviceInstance.terminate() to call
the StateEventHandler disposal before (or immediately after) calling
this._device.terminate() so no queued flush can emit 'stateEvent' for a
torn-down device.

---

Nitpick comments:
In `@packages/timeline-state-resolver/src/service/DeviceInstance.ts`:
- Around line 295-298: setEventSubscriptions currently accepts events: string[]
with no runtime validation, so typos silently fail because report(...) only
emits exact matches; update setEventSubscriptions to validate each entry against
the device's known/reportable events (e.g., use the device definition or the
same source used by report(...) to derive allowed event names) and emit a
warning via this.emit('warning', ...) or this.emit('debug', ...) for any
unknown/mismatched event names before delegating to
this._stateEventHandler.setEventSubscriptions(events); ensure you still call
setEventSubscriptions with the original array but include the diagnostic logging
for mismatches to aid debugging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3dc6edba-1449-4984-b08f-42aaa374566b

📥 Commits

Reviewing files that changed from the base of the PR and between 82b989f and cd848d3.

📒 Files selected for processing (2)
  • packages/timeline-state-resolver/src/service/DeviceInstance.ts
  • packages/timeline-state-resolver/src/service/__tests__/stateEventHandler.spec.ts

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants