Skip to content

feat: Status message customisation#418

Open
rjmunro wants to merge 23 commits intoSofie-Automation:mainfrom
bbc:rjmunro/error-message-customisation
Open

feat: Status message customisation#418
rjmunro wants to merge 23 commits intoSofie-Automation:mainfrom
bbc:rjmunro/error-message-customisation

Conversation

@rjmunro
Copy link
Copy Markdown
Contributor

@rjmunro rjmunro commented Jan 14, 2026

About the Contributor

This pull request is posted on behalf of the BBC.

Type of Contribution

This is a Feature.

Current Behavior

Errors are reported as plain strings. This makes them hard to understand in an machine readable way by e.g. sofie blueprints or translate.

New Behavior

Errors will include error codes and context as well as the message for backwards compatibility.

Testing Instructions

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 Jan 14, 2026

Walkthrough

Adds structured device status details and a DeviceStatusInput union; converts integrations to emit typed statusDetails[]; introduces per-integration status codes/messages and create<...>StatusDetail helpers; adds normalisation at DeviceInstance to produce full DeviceStatus with active; updates types, exports, and tests.

Changes

Cohort / File(s) Summary
Core types & template
packages/timeline-state-resolver-types/src/deviceStatusDetail.ts, packages/timeline-state-resolver-types/src/device.ts, packages/timeline-state-resolver-types/src/templateString.ts, packages/timeline-state-resolver-types/src/index.ts
Add DeviceStatusDetail interface; add optional statusDetails to DeviceStatus; add DeviceStatusInput union; change template interpolation API/behavior; re-export new modules.
Integration status-code types
packages/timeline-state-resolver-types/src/integrations/*.ts (atem, casparCG, httpWatcher, hyperdeck, lawo, obs, osc, panasonicPTZ, pharos, quantel, shotoku, sisyfos, sofieChef, telemetrics, tricaster, vizMSE, vmix, websocketClient, etc.)
Introduce per-integration *StatusCode, *StatusContextMap, *StatusDetail aliases and *StatusMessages template records.
Message factory helpers
packages/timeline-state-resolver/src/integrations/.../messages.ts (atem, casparCG, httpWatcher, hyperdeck, lawo, obs, osc, panasonicPTZ, pharos, quantel, shotoku, sisyfos, sofieChef, telemetrics, tricaster, vizMSE, vmix, websocketClient, etc.)
Add typed create<...>StatusDetail helpers that validate context and produce DeviceStatusDetail with interpolated message.
Device integrations (getStatus) migrations
packages/timeline-state-resolver/src/integrations/*/index.ts (httpWatcher, hyperdeck, casparCG, atem, osc, obs, lawo, panasonicPTZ, pharos, quantel, shotoku, sisyfos, sofieChef, telemetrics, tricaster, vizMSE, vmix, websocketClient, tcpSend, udpSend, etc.)
Change getStatus() return type to DeviceStatusInput; replace messages: string[] with statusDetails: DeviceStatusDetail[]; persist init options where needed to populate context; add defensive/null handling for optional connections.
Device core & normalization
packages/timeline-state-resolver/src/service/DeviceInstance.ts, packages/timeline-state-resolver/src/devices/device.ts, packages/timeline-state-resolver/src/integrations/abstract/index.ts
Introduce normaliseDeviceStatus to convert DeviceStatusInputDeviceStatus (ensuring active); update Device API types and connectionChanged to accept DeviceStatusInput; ensure deviceName present in DeviceContextAPI and adjust construction order.
WebSocket connection tracking
packages/timeline-state-resolver/src/integrations/websocketClient/connection.ts, .../index.ts, .../messages.ts
Track last error / disconnect reason; update connectionStatus() to return DeviceStatusInput with appropriate WebSocketClientStatusDetail codes (CONNECTION_FAILED / NOT_CONNECTED).
Public API & exports
packages/timeline-state-resolver/src/index.ts, packages/timeline-state-resolver-api/src/tsr-api.ts, packages/timeline-state-resolver-api/src/device.ts
Re-export new modules/classes; add DeviceStatusInput to public re-exports; update API types (DeviceContextAPI.deviceName, Device.getStatus(), connectionChanged payloads).
Tests & helpers
packages/timeline-state-resolver/src/integrations/*/__tests__/*.spec.ts, packages/timeline-state-resolver/src/integrations/__tests__/testlib.ts, packages/timeline-state-resolver/src/service/__tests__/deviceInstance.spec.ts, packages/timeline-state-resolver-types/src/__tests__/templateString.spec.ts
Update tests to assert statusDetails arrays and new template interpolation behavior; add deviceName to mocked DeviceContextAPI.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Device as Device (integration)
  participant DeviceInstance as DeviceInstance
  participant Normaliser as normaliseDeviceStatus
  participant EventBus as connectionChanged / Consumer

  Device->>DeviceInstance: getStatus(): DeviceStatusInput
  DeviceInstance->>Normaliser: normaliseDeviceStatus(DeviceStatusInput)
  Normaliser-->>DeviceInstance: DeviceStatus (statusCode + statusDetails + active)
  DeviceInstance->>EventBus: emit connectionChanged(DeviceStatus)
  EventBus->>Consumer: receive normalized DeviceStatus
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Contribution from BBC

Suggested reviewers

  • Julusian
  • nytamin
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Status message customisation' clearly and concisely summarizes the main feature: adding customizable status messages with structured error codes and context.
Description check ✅ Passed The description explains the motivation (machine-readable errors), current vs. new behavior, and indicates the contribution is on behalf of the BBC, though testing and documentation checklist items remain unchecked.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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.

@rjmunro
Copy link
Copy Markdown
Contributor Author

rjmunro commented Jan 14, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 14, 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: 3

🤖 Fix all issues with AI agents
In `@packages/timeline-state-resolver/README.md`:
- Around line 129-134: The example uses a placeholder comment for the context
parameter type in createMyDeviceError; replace it with a mapped type so the
context is strongly typed (e.g., use MyDeviceErrorContextMap[T] as the context
type) and ensure a MyDeviceErrorContextMap type exists mapping each
MyDeviceErrorCode to its context shape; update the function signature for
createMyDeviceError<T extends MyDeviceErrorCode>(code: T, context:
MyDeviceErrorContextMap[T]): DeviceStatusError<T> and import/declare
MyDeviceErrorContextMap where needed so the example compiles and gives concrete
types for each error code.

In `@packages/timeline-state-resolver/src/deviceErrorMessages.ts`:
- Around line 34-38: The JSDoc example output is inconsistent with the template
interpolation: update the example so the output matches the actual template
behavior used by errorsToMessages; either adjust the allMessages template for
AtemErrorCode.DISCONNECTED to not include {{host}} or change the expected
messages array to include the host (e.g., "ATEM disconnected from
192.168.1.10"); reference the AtemErrorCode.DISCONNECTED entry and the
errorsToMessages/allMessages usage in the example and ensure the example's input
context (host) and expected output align.

In `@packages/timeline-state-resolver/src/index.ts`:
- Around line 12-18: The export lists a non-existent symbol AtemErrorContext
causing the build to fail; update the export block to remove AtemErrorContext
and instead export the actual context types exported by the errors module
(AtemErrorContextMap, AtemDisconnectedContext, AtemPsuFaultContext) or re-export
whichever specific context aliases you need (e.g., export type AtemErrorContext
= AtemErrorContextMap if you prefer a single alias), ensuring the exported
symbols match those declared in the './integrations/atem/errors' module.
🧹 Nitpick comments (1)
packages/timeline-state-resolver/src/__tests__/deviceErrorMessages.spec.ts (1)

11-13: Minor: Test context differs from actual definition.

The test defines host: string as required, but in errors.ts, AtemDisconnectedContext has host?: string (optional). This inconsistency is minor since it doesn't affect test correctness, but consider aligning them for consistency.

♻️ Suggested fix
 interface AtemDisconnectedContext extends DeviceErrorContextBase {
-	host: string
+	host?: string
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dddc23e and 4031274.

📒 Files selected for processing (10)
  • packages/timeline-state-resolver-types/src/device.ts
  • packages/timeline-state-resolver-types/src/deviceError.ts
  • packages/timeline-state-resolver-types/src/index.ts
  • packages/timeline-state-resolver/README.md
  • packages/timeline-state-resolver/src/__tests__/deviceErrorMessages.spec.ts
  • packages/timeline-state-resolver/src/deviceErrorMessages.ts
  • packages/timeline-state-resolver/src/index.ts
  • packages/timeline-state-resolver/src/integrations/atem/__tests__/atem.spec.ts
  • packages/timeline-state-resolver/src/integrations/atem/errors.ts
  • packages/timeline-state-resolver/src/integrations/atem/index.ts
🧰 Additional context used
🧬 Code graph analysis (4)
packages/timeline-state-resolver-types/src/device.ts (1)
packages/timeline-state-resolver-types/src/deviceError.ts (1)
  • DeviceStatusError (27-35)
packages/timeline-state-resolver/src/deviceErrorMessages.ts (1)
packages/timeline-state-resolver-types/src/deviceError.ts (1)
  • DeviceStatusError (27-35)
packages/timeline-state-resolver/src/integrations/atem/errors.ts (1)
packages/timeline-state-resolver-types/src/deviceError.ts (2)
  • DeviceErrorContextBase (5-8)
  • DeviceStatusError (27-35)
packages/timeline-state-resolver/src/__tests__/deviceErrorMessages.spec.ts (3)
packages/timeline-state-resolver/src/index.ts (2)
  • AtemErrorCode (14-14)
  • AtemErrorMessages (15-15)
packages/timeline-state-resolver-types/src/deviceError.ts (2)
  • DeviceErrorContextBase (5-8)
  • DeviceStatusError (27-35)
packages/timeline-state-resolver/src/deviceErrorMessages.ts (1)
  • errorsToMessages (40-48)
🪛 GitHub Actions: Node CI
packages/timeline-state-resolver-types/src/deviceError.ts

[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver/README.md

[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver/src/index.ts

[error] 17-17: "./integrations/atem/errors" has no exported member named 'AtemErrorContext'. Did you mean 'AtemErrorContextMap'?


[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver/src/integrations/atem/index.ts

[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver-types/src/index.ts

[error] 17-17: "./integrations/atem/errors" has no exported member named 'AtemErrorContext'. Did you mean 'AtemErrorContextMap'?


[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver-types/src/device.ts

[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver/src/deviceErrorMessages.ts

[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver/src/integrations/atem/errors.ts

[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver/src/__tests__/deviceErrorMessages.spec.ts

[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

packages/timeline-state-resolver/src/integrations/atem/__tests__/atem.spec.ts

[error] 1-1: Build failed for timeline-state-resolver:build. Process completed with exit code 130.

🔇 Additional comments (12)
packages/timeline-state-resolver-types/src/index.ts (1)

55-55: LGTM!

The new export follows the established pattern and correctly exposes the deviceError module types for consumers.

packages/timeline-state-resolver/README.md (1)

49-88: Good documentation for the new feature.

The documentation clearly explains the structured error system, provides usage examples for blueprints, and shows how to convert errors to messages. This will help consumers understand and adopt the new error handling approach.

packages/timeline-state-resolver-types/src/deviceError.ts (1)

1-35: Well-designed type definitions.

The generic interface design with sensible defaults provides flexibility for device-specific implementations while maintaining type safety. The JSDoc documentation clearly explains the naming convention and usage patterns.

packages/timeline-state-resolver-types/src/device.ts (1)

3-3: Clean extension of DeviceStatus.

The optional errors field maintains backward compatibility while enabling the new structured error system. The documentation clearly explains the fallback behavior when errors are not present or understood.

Also applies to: 15-24

packages/timeline-state-resolver/src/deviceErrorMessages.ts (1)

9-17: Solid implementation.

The interpolate function correctly preserves placeholders when values are missing, providing graceful degradation. The errorsToMessages function has a sensible fallback to the error code when no template is found.

Also applies to: 40-47

packages/timeline-state-resolver/src/integrations/atem/errors.ts (1)

1-68: Well-structured type-safe error system.

The implementation follows TypeScript best practices:

  • Const object + derived type pattern for error codes ensures type safety
  • Context interfaces properly extend DeviceErrorContextBase
  • Factory function with generics enforces correct context types per error code
  • Message templates use consistent {{variable}} interpolation syntax
packages/timeline-state-resolver/src/index.ts (1)

20-37: TODO comment is reasonable for tracking future work.

The TODO provides a clear pattern reference and lists all remaining integrations. Consider creating an issue to track this work instead of leaving it in code long-term.

packages/timeline-state-resolver/src/__tests__/deviceErrorMessages.spec.ts (1)

58-148: Comprehensive test coverage for errorsToMessages.

Tests cover all essential scenarios:

  • Single and multiple error conversion
  • Variable interpolation from context
  • Custom template overrides
  • Fallback to raw error code when no template exists
  • Missing placeholder handling
  • Empty array edge case
  • Optional deviceName/deviceId fields
  • Cross-device template combination
packages/timeline-state-resolver/src/integrations/atem/__tests__/atem.spec.ts (2)

2-2: Import will work once the build issue in index.ts is resolved.

The import from '..' correctly references the re-exported AtemErrorCode and createAtemError from the ATEM integration barrel.


57-121: Thorough test coverage for the new error structure.

The 'Check Status' test comprehensively validates:

  • Initial disconnected state produces DISCONNECTED error
  • Connected state clears errors to undefined
  • PSU faults generate PSU_FAULT errors with correct context (psuNumber, totalPsus)
  • Reconnection/disconnect transitions

The assertions for both messages and errors arrays ensure backward compatibility while validating the new structured error system.

packages/timeline-state-resolver/src/integrations/atem/index.ts (2)

39-43: Clean separation of error imports and re-exports.

The error-related imports and exports are well-organized, making the error types available for blueprint customization while keeping the implementation details in errors.ts.


158-188: Well-structured error accumulation and status reporting.

The refactored getStatus() method:

  • Properly accumulates errors into a typed array
  • Uses early return for disconnected state (appropriate optimization)
  • Iterates PSUs with 1-indexed display for human-readable messages
  • Derives messages via errorsToMessages for consistency
  • Returns errors: undefined when empty (backward compatible)

The status code logic correctly prioritizes: BAD (disconnected) > WARNING_MAJOR (PSU fault) > GOOD.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread packages/timeline-state-resolver/README.md Outdated
Comment thread packages/timeline-state-resolver/src/deviceErrorMessages.ts Outdated
Comment thread packages/timeline-state-resolver/src/index.ts Outdated
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch 5 times, most recently from f3d719f to 2d26d58 Compare January 16, 2026 18:07
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch 5 times, most recently from 687fb30 to 099da5e Compare January 22, 2026 12:48
@rjmunro
Copy link
Copy Markdown
Contributor Author

rjmunro commented Jan 22, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 22, 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: 5

🤖 Fix all issues with AI agents
In `@packages/timeline-state-resolver-types/src/index.ts`:
- Around line 29-31: Duplicate export of './integrations/atem' is present;
remove the redundant export line so './integrations/atem' is only exported once
(keep a single export statement for the ATEM integration in index.ts to avoid
duplicate re-exports).

In `@packages/timeline-state-resolver/src/integrations/casparCG/errors.ts`:
- Around line 8-12: The JSDoc example for createCasparCGError uses
CasparCGErrorCode.QUEUE_OVERFLOW but omits required context fields; update the
example to supply the full CasparCGErrorContextMap for QUEUE_OVERFLOW (include
deviceName, host, and port) so it matches the required shape, i.e., modify the
example call to createCasparCGError(CasparCGErrorCode.QUEUE_OVERFLOW, {
deviceName: 'CasparCG 1', host: '192.168.1.10', port: 5250 }) to mirror the
DISCONNECTED example and ensure consistency with CasparCGErrorContextMap.

In `@packages/timeline-state-resolver/src/integrations/casparCG/index.ts`:
- Around line 788-792: The returned status object currently always includes an
errors array which can be empty and is truthy; in the function that returns the
object (the block using statusCode, messages: errorsToMessages(errors,
CasparCGErrorMessages), errors, active: this.isActive) change the construction
so that the errors property is only added when errors.length > 0 (and likewise
omit messages if errors is empty), e.g. build the result object conditionally
and only assign errors when non-empty to match the ATEM integration and avoid
consumers misinterpreting an empty array as an error.
- Around line 760-785: The getStatus() method dereferences this._ccg.host and
this._ccg.port which can be undefined before init() or during teardown; update
getStatus() to safely read host/port (e.g., const host = this._ccg?.host ??
this.initOptions?.host ?? '' and const port = this._ccg?.port ??
this.initOptions?.port ?? '') and use those fallback values when calling
createCasparCGError(CasparCGErrorCode.DISCONNECTED, ...) and
createCasparCGError(CasparCGErrorCode.QUEUE_OVERFLOW, ...) so no property access
on an uninitialized this._ccg occurs.

In `@packages/timeline-state-resolver/src/service/DeviceInstance.ts`:
- Around line 269-272: The constructor currently calls new
deviceSpecs.deviceClass(...) which triggers _getDeviceContextAPI() and reads
this._deviceName before _deviceName is assigned; move the assignment that sets
this._deviceName to occur before instantiating deviceSpecs.deviceClass (i.e.,
set this._deviceName early in the constructor), so _getDeviceContextAPI()
returns the correct name; update any constructor flow that builds the
DeviceContextAPI or calls _getDeviceContextAPI() to happen after this
assignment.
🧹 Nitpick comments (1)
packages/timeline-state-resolver-types/src/templateString.ts (1)

22-29: Consider removing or conditionalizing the console.log for missing values.

Line 25 logs a message for every missing placeholder value. This could be noisy in production, especially if:

  • Custom templates intentionally use fewer variables than available in context
  • Templates are processed frequently

Since this is expected behavior when leaveUnknownPlaceholders is true (placeholders are preserved for later substitution), the log may not always indicate a problem.

♻️ Consider removing or making logging conditional
 export function interpolateTemplateString(
 	template: string,
 	context: Record<string, any>,
-	leaveUnknownPlaceholders = false
+	leaveUnknownPlaceholders = false,
+	debug = false
 ): string {
 	return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
 		const value = context[key]
 		if (value === undefined || value === null) {
-			console.log(`interpolateTemplateString: missing value for key ${key} in template:`, template)
+			if (debug) {
+				console.log(`interpolateTemplateString: missing value for key ${key} in template:`, template)
+			}
 			return leaveUnknownPlaceholders ? match : key // Keep key or placeholder if no value
 		}
 		return String(value)
 	})
 }

Alternatively, remove the log entirely since the behavior (preserving placeholder or using key name) is well-documented.

Comment thread packages/timeline-state-resolver-types/src/index.ts Outdated
Comment thread packages/timeline-state-resolver/src/integrations/casparCG/index.ts Outdated
Comment thread packages/timeline-state-resolver/src/integrations/casparCG/index.ts Outdated
Comment thread packages/timeline-state-resolver/src/service/DeviceInstance.ts Outdated
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch 3 times, most recently from b76e7c4 to 46cf25d Compare January 27, 2026 14:21
* These codes can be customized in blueprints via deviceErrorMessages.
*/
export const AtemErrorCode = {
DISCONNECTED: 'DEVICE_ATEM_DISCONNECTED',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here they are DEVICE_ATEM_DISCONNECTED, in some others they are HTTPWATCHER_KEYWORD_NOT_FOUND. It would be good for that to be consistent

Copy link
Copy Markdown
Contributor Author

@rjmunro rjmunro Jan 28, 2026

Choose a reason for hiding this comment

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

Changed to always start DEVICE_

Comment thread packages/timeline-state-resolver-types/src/device.ts Outdated
Comment thread packages/timeline-state-resolver/src/index.ts Outdated
Comment thread packages/timeline-state-resolver/src/deviceErrorMessages.ts Outdated
Comment thread packages/timeline-state-resolver-types/src/device.ts Outdated
Comment thread packages/timeline-state-resolver/README.md Outdated
Comment thread packages/timeline-state-resolver/README.md Outdated
Comment thread packages/timeline-state-resolver/README.md Outdated
Comment thread packages/timeline-state-resolver/README.md Outdated
Comment thread packages/timeline-state-resolver/README.md Outdated
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch 5 times, most recently from bea7594 to ec334bd Compare February 3, 2026 10:14
@rjmunro rjmunro marked this pull request as ready for review February 3, 2026 10:15
@rjmunro rjmunro requested a review from a team as a code owner February 3, 2026 10:15
@rjmunro
Copy link
Copy Markdown
Contributor Author

rjmunro commented Feb 3, 2026

@coderabbitai review

@rjmunro rjmunro changed the title feat: Error message customisation feat: Status message customisation Apr 28, 2026
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch from 9646900 to 61ea489 Compare April 29, 2026 11:34
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: 2

♻️ Duplicate comments (5)
packages/timeline-state-resolver-types/src/device.ts (1)

21-27: ⚠️ Potential issue | 🟠 Major

Document statusDetails capability semantics explicitly.

The doc should explicitly define statusDetails: undefined (not supported) vs statusDetails: [] (supported but no active details). Without that, consumers cannot reliably feature-detect structured status support from DeviceStatus alone.

📝 Suggested doc update
 	/**
 	 * Structured status details.
+	 * Feature-detection semantics:
+	 * - `statusDetails: undefined` => structured status details are not supported by this device.
+	 * - `statusDetails: []` => structured status details are supported, but none are currently active.
+	 *
+	 * If `statusDetails` is `undefined`, consumers should fall back to `messages`.
 	 * Each detail carries a pre-rendered message and, for devices that support it, a status code
 	 * and context object that consumers can use to apply custom message templates via
 	 * interpolateTemplateString().
 	 */
Based on learnings: In the DeviceStatus interface, an empty `errors`/details array indicates structured support, while `undefined` indicates no structured support and consumers should fall back to plain messages.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver-types/src/device.ts` around lines 21 - 27,
Update the JSDoc for the DeviceStatus.statusDetails property to explicitly state
capability semantics: document that statusDetails === undefined means the device
does not support structured status details (consumers must fall back to plain
messages), while statusDetails === [] means the device supports structured
details but currently has no active details; reference DeviceStatus,
statusDetails, and DeviceStatusDetail and mention consumers can use
interpolateTemplateString() when structured details are present.
packages/timeline-state-resolver/src/integrations/osc/index.ts (1)

202-212: ⚠️ Potential issue | 🟡 Minor

Use runtime device name in OSC status context.

Line 202 hardcodes "OSC", so renamed device instances lose identity in templated status messages.

🔧 Proposed fix
-		const deviceName = 'OSC'
+		const deviceName = this.context.deviceName ?? 'OSC'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/integrations/osc/index.ts` around lines
202 - 212, The status code uses a hardcoded deviceName = 'OSC' so named
instances lose their identity; change deviceName to derive from the runtime
options (e.g. const deviceName = this.options?.deviceName ?? this.options?.name
?? 'OSC') and use that when calling createOSCStatusDetail for
OSCStatusCode.TCP_DISCONNECTED (and any other places that use deviceName) so
templated status messages reflect the actual device instance.
packages/timeline-state-resolver/src/integrations/panasonicPTZ/index.ts (1)

141-149: ⚠️ Potential issue | 🟡 Minor

Avoid hardcoded device identity and nullable context in PTZ status detail.

Line 141 should use the runtime instance name, and Lines 148-149 should fallback to stable values to avoid rendering "undefined" in messages.

🔧 Proposed fix
-		const deviceName = 'Panasonic PTZ'
+		const deviceName = this.context.deviceName ?? 'Panasonic PTZ'
@@
-					host: this._host,
-					port: this._port,
+					host: this._host ?? '',
+					port: this._port ?? 0,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/integrations/panasonicPTZ/index.ts`
around lines 141 - 149, Replace the hardcoded deviceName and nullable host/port
in the NOT_CONNECTED status path: compute deviceName from the runtime instance
(e.g. const deviceName = this._device?.name ?? 'Panasonic PTZ' or similar) and
avoid optional chaining inside the check (use if (!(this._device &&
this._device.connected)) or equivalent). When calling
createPanasonicPTZStatusDetail(PanasonicPTZStatusCode.NOT_CONNECTED, {...}),
pass stable fallbacks for host and port (for example this._host ?? 'unknown
host' and this._port ?? 'unknown port') so the status detail never contains
"undefined".
packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts (1)

261-269: ⚠️ Potential issue | 🟡 Minor

Use runtime device name in status context.

Line 261 hardcodes 'Hyperdeck', so status detail context can be wrong for renamed instances.

🔧 Proposed fix
-		const deviceName = 'Hyperdeck'
+		const deviceName = this.context.deviceName
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts` around
lines 261 - 269, The status context currently hardcodes deviceName = 'Hyperdeck'
causing incorrect names for renamed instances; change the deviceName assignment
used in the NOT_CONNECTED status block to use the runtime name (for example:
this.deviceName ?? this._initOptions?.deviceName ?? 'Hyperdeck') so
createHyperdeckStatusDetail(HyperdeckStatusCode.NOT_CONNECTED, {...}) passes the
actual instance name instead of the literal string.
packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts (1)

59-68: ⚠️ Potential issue | 🟠 Major

Redact and cap response data before storing it in statusDetails.

REQUEST_ERROR, UNEXPECTED_STATUS_CODE, and KEYWORD_NOT_FOUND still copy raw response bodies/headers into the emitted status payload. These details now flow through connectionChanged, so they can leak cookies or other sensitive values and can grow without bound on large responses. Please sanitize headers and truncate body previews before calling createHTTPWatcherStatusDetail(...).

Also applies to: 91-107

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

In `@packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts`
around lines 59 - 68, The status payload currently copies raw response
bodies/headers into statusDetails for HTTPWatcher errors (e.g., when handling
REQUEST_ERROR in the catch block that calls this._setStatus(StatusCode.BAD,
[createHTTPWatcherStatusDetail(HTTPWatcherStatusCode.REQUEST_ERROR, context)])
and the similar UNEXPECTED_STATUS_CODE / KEYWORD_NOT_FOUND paths); before
building the context passed to createHTTPWatcherStatusDetail, sanitize headers
(redact cookie/set-cookie and other sensitive header values) and truncate body
previews to a reasonable max length (e.g., 1–2KB or configurable) and replace
binary/long content with a summarized placeholder; apply the same
redaction/truncation logic wherever createHTTPWatcherStatusDetail is invoked
(including the blocks around lines 91–107) so statusDetails never contain
unbounded or sensitive raw response data.
🧹 Nitpick comments (3)
packages/timeline-state-resolver-types/src/integrations/hyperdeck.ts (1)

55-58: Consider using available context in default connectivity message.

NOT_CONNECTED currently discards deviceName/host/port, which makes default diagnostics less actionable than the typed context allows.

💡 Proposed refinement
 export const HyperdeckStatusMessages: Record<HyperdeckStatusCode, string> = {
-	[HyperdeckStatusCode.NOT_CONNECTED]: 'Not connected',
+	[HyperdeckStatusCode.NOT_CONNECTED]: '{{deviceName}} not connected ({{host}}:{{port}})',
 	[HyperdeckStatusCode.LOW_RECORDING_TIME]:
 		'Recording time left is less than {{minutes}} minutes and {{seconds}} seconds',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver-types/src/integrations/hyperdeck.ts` around
lines 55 - 58, The default message for HyperdeckStatusCode.NOT_CONNECTED
discards context (deviceName/host/port); update the mapping entry for
HyperdeckStatusCode.NOT_CONNECTED to include contextual placeholders (e.g. 'Not
connected to {{deviceName}} ({{host}}:{{port}})' or similar) so the formatted
message uses the available context when rendered; ensure any formatter that
consumes this map (the code that calls the status->message lookup and
interpolates with the provided context) still supplies the same keys
(deviceName, host, port) so the new placeholders resolve correctly.
packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts (1)

258-335: Split getStatus() into smaller helpers to reduce complexity.

This method is doing multiple independent checks; extracting each check into helper methods will make this easier to test/maintain and should satisfy the cognitive complexity threshold.

♻️ Refactor sketch
-	getStatus(): DeviceStatusInput {
-		let statusCode = StatusCode.GOOD
-		const statusDetails: DeviceStatusDetail[] = []
-		const deviceName = 'Hyperdeck'
-		...
-		return {
-			statusCode,
-			statusDetails,
-		}
-	}
+	getStatus(): DeviceStatusInput {
+		const deviceName = this.context.deviceName
+		const status = this._createBaseStatus()
+
+		if (!this._connected) {
+			this._appendNotConnectedStatus(status, deviceName)
+			return status
+		}
+
+		this._appendRecordingTimeStatus(status, deviceName)
+		this._appendSlotStatus(status, deviceName)
+		this._appendTransportStatus(status, deviceName)
+
+		return status
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts` around
lines 258 - 335, The getStatus() function is doing multiple independent checks;
refactor by extracting each check into small helper methods: create
checkConnectionStatus() that returns {statusCode?: StatusCode, statusDetails?:
DeviceStatusDetail[]} for connection/not-connected logic (uses this._connected,
this._initOptions), checkRecordingTimeStatus() for the
_minRecordingTime/_recordingTime logic (returns optional statusCode and detail
created by createHyperdeckStatusDetail with
HyperdeckStatusCode.LOW_RECORDING_TIME), checkSlotAvailabilityStatus() that
iterates slots 1..this._slotCount using this._slotStatus and
this._suppressEmptySlotWarnings and returns aggregated statusCode/statusDetails
(uses HyperdeckStatusCode.SLOT_NOT_MOUNTED), and checkTransportStatus() that
compares this._expectedTransportStatus vs this._transportStatus and returns
appropriate statusCode/statusDetails (uses TransportStatus.RECORD/PLAY and
HyperdeckStatusCode.NOT_RECORDING/NOT_PLAYING); then simplify getStatus() to
call these helpers, merge their statusDetails into one array, and compute the
highest severity statusCode to return.
packages/timeline-state-resolver/src/integrations/telemetrics/index.ts (1)

52-52: Use Telemetrics-specific detail typing for stronger safety.

statusDetails is currently DeviceStatusDetail[], which loses Telemetrics code/context coupling at compile time. Prefer TelemetricsStatusDetail[].

Suggested diff
 import {
 	DeviceStatusInput,
-	DeviceStatusDetail,
+	TelemetricsStatusDetail,
 	Mappings,
 	StatusCode,
 	TelemetricsDeviceTypes,
 	TelemetricsOptions,
 	TimelineContentTelemetrics,
 	TSRTimelineContent,
 	TelemetricsStatusCode,
 } from 'timeline-state-resolver-types'
@@
-		const statusDetails: DeviceStatusDetail[] = []
+		const statusDetails: TelemetricsStatusDetail[] = []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/integrations/telemetrics/index.ts` at
line 52, Change the statusDetails declaration to use the Telemetrics-specific
type so Telemetrics-only fields are preserved: replace the array type
DeviceStatusDetail[] with TelemetricsStatusDetail[] for the statusDetails
variable in the integrations/telemetrics module (look for the const
statusDetails declaration in index.ts) and update any downstream usages or
imports if necessary to reference TelemetricsStatusDetail instead of
DeviceStatusDetail.
🤖 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/src/integrations/sofieChef/index.ts`:
- Around line 208-215: The status block currently hardcodes deviceName as
'SofieChef'; change it to use the runtime/configured device name (e.g. the
instance property or config field used by this integration) so status messages
reflect the actual device. Locate the block around the connected check
(references: this.connected, statusCode, statusDetails,
createSofieChefStatusDetail, SofieChefStatusCode.NOT_CONNECTED) and replace the
literal deviceName with the appropriate runtime value (for example
this.deviceName or the config's deviceName property) so the object passed to
createSofieChefStatusDetail contains the real device name.

In `@packages/timeline-state-resolver/src/service/DeviceInstance.ts`:
- Around line 31-45: normaliseDeviceStatus currently fabricates statusDetails
from messages for legacy devices; change it so that when input lacks
statusDetails you return statusDetails: undefined (not an array) so callers can
detect legacy devices—update the function normaliseDeviceStatus to copy
statusDetails when present and leave statusDetails undefined otherwise,
preserving messages and active; also update tests in deviceInstance.spec.ts to
add assertions for both cases (statusDetails undefined vs statusDetails === []
for integrations that support structured errors) to cover the empty-array vs
undefined distinction and ensure consumers fall back to messages when
statusDetails is undefined.

---

Duplicate comments:
In `@packages/timeline-state-resolver-types/src/device.ts`:
- Around line 21-27: Update the JSDoc for the DeviceStatus.statusDetails
property to explicitly state capability semantics: document that statusDetails
=== undefined means the device does not support structured status details
(consumers must fall back to plain messages), while statusDetails === [] means
the device supports structured details but currently has no active details;
reference DeviceStatus, statusDetails, and DeviceStatusDetail and mention
consumers can use interpolateTemplateString() when structured details are
present.

In `@packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts`:
- Around line 59-68: The status payload currently copies raw response
bodies/headers into statusDetails for HTTPWatcher errors (e.g., when handling
REQUEST_ERROR in the catch block that calls this._setStatus(StatusCode.BAD,
[createHTTPWatcherStatusDetail(HTTPWatcherStatusCode.REQUEST_ERROR, context)])
and the similar UNEXPECTED_STATUS_CODE / KEYWORD_NOT_FOUND paths); before
building the context passed to createHTTPWatcherStatusDetail, sanitize headers
(redact cookie/set-cookie and other sensitive header values) and truncate body
previews to a reasonable max length (e.g., 1–2KB or configurable) and replace
binary/long content with a summarized placeholder; apply the same
redaction/truncation logic wherever createHTTPWatcherStatusDetail is invoked
(including the blocks around lines 91–107) so statusDetails never contain
unbounded or sensitive raw response data.

In `@packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts`:
- Around line 261-269: The status context currently hardcodes deviceName =
'Hyperdeck' causing incorrect names for renamed instances; change the deviceName
assignment used in the NOT_CONNECTED status block to use the runtime name (for
example: this.deviceName ?? this._initOptions?.deviceName ?? 'Hyperdeck') so
createHyperdeckStatusDetail(HyperdeckStatusCode.NOT_CONNECTED, {...}) passes the
actual instance name instead of the literal string.

In `@packages/timeline-state-resolver/src/integrations/osc/index.ts`:
- Around line 202-212: The status code uses a hardcoded deviceName = 'OSC' so
named instances lose their identity; change deviceName to derive from the
runtime options (e.g. const deviceName = this.options?.deviceName ??
this.options?.name ?? 'OSC') and use that when calling createOSCStatusDetail for
OSCStatusCode.TCP_DISCONNECTED (and any other places that use deviceName) so
templated status messages reflect the actual device instance.

In `@packages/timeline-state-resolver/src/integrations/panasonicPTZ/index.ts`:
- Around line 141-149: Replace the hardcoded deviceName and nullable host/port
in the NOT_CONNECTED status path: compute deviceName from the runtime instance
(e.g. const deviceName = this._device?.name ?? 'Panasonic PTZ' or similar) and
avoid optional chaining inside the check (use if (!(this._device &&
this._device.connected)) or equivalent). When calling
createPanasonicPTZStatusDetail(PanasonicPTZStatusCode.NOT_CONNECTED, {...}),
pass stable fallbacks for host and port (for example this._host ?? 'unknown
host' and this._port ?? 'unknown port') so the status detail never contains
"undefined".

---

Nitpick comments:
In `@packages/timeline-state-resolver-types/src/integrations/hyperdeck.ts`:
- Around line 55-58: The default message for HyperdeckStatusCode.NOT_CONNECTED
discards context (deviceName/host/port); update the mapping entry for
HyperdeckStatusCode.NOT_CONNECTED to include contextual placeholders (e.g. 'Not
connected to {{deviceName}} ({{host}}:{{port}})' or similar) so the formatted
message uses the available context when rendered; ensure any formatter that
consumes this map (the code that calls the status->message lookup and
interpolates with the provided context) still supplies the same keys
(deviceName, host, port) so the new placeholders resolve correctly.

In `@packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts`:
- Around line 258-335: The getStatus() function is doing multiple independent
checks; refactor by extracting each check into small helper methods: create
checkConnectionStatus() that returns {statusCode?: StatusCode, statusDetails?:
DeviceStatusDetail[]} for connection/not-connected logic (uses this._connected,
this._initOptions), checkRecordingTimeStatus() for the
_minRecordingTime/_recordingTime logic (returns optional statusCode and detail
created by createHyperdeckStatusDetail with
HyperdeckStatusCode.LOW_RECORDING_TIME), checkSlotAvailabilityStatus() that
iterates slots 1..this._slotCount using this._slotStatus and
this._suppressEmptySlotWarnings and returns aggregated statusCode/statusDetails
(uses HyperdeckStatusCode.SLOT_NOT_MOUNTED), and checkTransportStatus() that
compares this._expectedTransportStatus vs this._transportStatus and returns
appropriate statusCode/statusDetails (uses TransportStatus.RECORD/PLAY and
HyperdeckStatusCode.NOT_RECORDING/NOT_PLAYING); then simplify getStatus() to
call these helpers, merge their statusDetails into one array, and compute the
highest severity statusCode to return.

In `@packages/timeline-state-resolver/src/integrations/telemetrics/index.ts`:
- Line 52: Change the statusDetails declaration to use the Telemetrics-specific
type so Telemetrics-only fields are preserved: replace the array type
DeviceStatusDetail[] with TelemetricsStatusDetail[] for the statusDetails
variable in the integrations/telemetrics module (look for the const
statusDetails declaration in index.ts) and update any downstream usages or
imports if necessary to reference TelemetricsStatusDetail instead of
DeviceStatusDetail.
🪄 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: 3e1772ce-096c-4fb9-8e4e-118eb93b6f57

📥 Commits

Reviewing files that changed from the base of the PR and between 9646900 and 61ea489.

⛔ Files ignored due to path filters (2)
  • packages/timeline-state-resolver-types/src/__tests__/__snapshots__/index.spec.ts.snap is excluded by !**/*.snap
  • packages/timeline-state-resolver/src/__tests__/__snapshots__/index.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (82)
  • packages/timeline-state-resolver-api/src/device.ts
  • packages/timeline-state-resolver-api/src/tsr-api.ts
  • packages/timeline-state-resolver-types/src/__tests__/templateString.spec.ts
  • packages/timeline-state-resolver-types/src/device.ts
  • packages/timeline-state-resolver-types/src/deviceStatusDetail.ts
  • packages/timeline-state-resolver-types/src/index.ts
  • packages/timeline-state-resolver-types/src/integrations/atem.ts
  • packages/timeline-state-resolver-types/src/integrations/casparcg.ts
  • packages/timeline-state-resolver-types/src/integrations/httpWatcher.ts
  • packages/timeline-state-resolver-types/src/integrations/hyperdeck.ts
  • packages/timeline-state-resolver-types/src/integrations/lawo.ts
  • packages/timeline-state-resolver-types/src/integrations/obs.ts
  • packages/timeline-state-resolver-types/src/integrations/osc.ts
  • packages/timeline-state-resolver-types/src/integrations/panasonicPTZ.ts
  • packages/timeline-state-resolver-types/src/integrations/pharos.ts
  • packages/timeline-state-resolver-types/src/integrations/quantel.ts
  • packages/timeline-state-resolver-types/src/integrations/shotoku.ts
  • packages/timeline-state-resolver-types/src/integrations/sisyfos.ts
  • packages/timeline-state-resolver-types/src/integrations/sofieChef.ts
  • packages/timeline-state-resolver-types/src/integrations/telemetrics.ts
  • packages/timeline-state-resolver-types/src/integrations/tricaster.ts
  • packages/timeline-state-resolver-types/src/integrations/vizMSE.ts
  • packages/timeline-state-resolver-types/src/integrations/vmix.ts
  • packages/timeline-state-resolver-types/src/integrations/websocketClient.ts
  • packages/timeline-state-resolver-types/src/templateString.ts
  • packages/timeline-state-resolver/src/devices/device.ts
  • packages/timeline-state-resolver/src/index.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/__tests__/atem.spec.ts
  • packages/timeline-state-resolver/src/integrations/atem/index.ts
  • packages/timeline-state-resolver/src/integrations/atem/messages.ts
  • packages/timeline-state-resolver/src/integrations/casparCG/index.ts
  • packages/timeline-state-resolver/src/integrations/casparCG/messages.ts
  • packages/timeline-state-resolver/src/integrations/httpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/__tests__/httpwatcher.spec.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/messages.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/__tests__/hyperdeck.spec.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/messages.ts
  • packages/timeline-state-resolver/src/integrations/kairos/index.ts
  • packages/timeline-state-resolver/src/integrations/lawo/index.ts
  • packages/timeline-state-resolver/src/integrations/lawo/messages.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/obs/messages.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/osc/messages.ts
  • packages/timeline-state-resolver/src/integrations/panasonicPTZ/index.ts
  • packages/timeline-state-resolver/src/integrations/panasonicPTZ/messages.ts
  • packages/timeline-state-resolver/src/integrations/pharos/index.ts
  • packages/timeline-state-resolver/src/integrations/pharos/messages.ts
  • packages/timeline-state-resolver/src/integrations/quantel/index.ts
  • packages/timeline-state-resolver/src/integrations/quantel/messages.ts
  • packages/timeline-state-resolver/src/integrations/shotoku/index.ts
  • packages/timeline-state-resolver/src/integrations/shotoku/messages.ts
  • packages/timeline-state-resolver/src/integrations/singularLive/index.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/index.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/messages.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/__tests__/sofieChef.spec.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/index.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/messages.ts
  • packages/timeline-state-resolver/src/integrations/tcpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/__tests__/telemetrics.spec.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/index.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/messages.ts
  • packages/timeline-state-resolver/src/integrations/tricaster/index.ts
  • packages/timeline-state-resolver/src/integrations/tricaster/messages.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/vizMSE/index.ts
  • packages/timeline-state-resolver/src/integrations/vizMSE/messages.ts
  • packages/timeline-state-resolver/src/integrations/vmix/index.ts
  • packages/timeline-state-resolver/src/integrations/vmix/messages.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/__tests__/websocketClient.spec.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/connection.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/index.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/messages.ts
  • packages/timeline-state-resolver/src/service/DeviceInstance.ts
  • packages/timeline-state-resolver/src/service/__tests__/deviceInstance.spec.ts
✅ Files skipped from review due to trivial changes (16)
  • packages/timeline-state-resolver/src/integrations/tests/testlib.ts
  • packages/timeline-state-resolver/src/service/tests/deviceInstance.spec.ts
  • packages/timeline-state-resolver/src/integrations/obs/messages.ts
  • packages/timeline-state-resolver/src/integrations/singularLive/index.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/messages.ts
  • packages/timeline-state-resolver/src/index.ts
  • packages/timeline-state-resolver/src/integrations/shotoku/messages.ts
  • packages/timeline-state-resolver/src/integrations/httpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/messages.ts
  • packages/timeline-state-resolver-types/src/integrations/lawo.ts
  • packages/timeline-state-resolver/src/devices/device.ts
  • packages/timeline-state-resolver-types/src/integrations/websocketClient.ts
  • packages/timeline-state-resolver-types/src/integrations/sisyfos.ts
  • packages/timeline-state-resolver-types/src/integrations/tricaster.ts
  • packages/timeline-state-resolver-types/src/integrations/httpWatcher.ts
  • packages/timeline-state-resolver-types/src/integrations/pharos.ts
🚧 Files skipped from review as they are similar to previous changes (27)
  • packages/timeline-state-resolver-types/src/deviceStatusDetail.ts
  • packages/timeline-state-resolver/src/integrations/ograf/index.ts
  • packages/timeline-state-resolver/src/integrations/tricaster/messages.ts
  • packages/timeline-state-resolver-types/src/tests/templateString.spec.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/messages.ts
  • packages/timeline-state-resolver/src/integrations/osc/messages.ts
  • packages/timeline-state-resolver/src/integrations/lawo/messages.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/tests/sofieChef.spec.ts
  • packages/timeline-state-resolver/src/integrations/panasonicPTZ/messages.ts
  • packages/timeline-state-resolver/src/integrations/quantel/messages.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/messages.ts
  • packages/timeline-state-resolver/src/integrations/atem/messages.ts
  • packages/timeline-state-resolver/src/integrations/udpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/abstract/index.ts
  • packages/timeline-state-resolver/src/integrations/vmix/index.ts
  • packages/timeline-state-resolver-types/src/integrations/vmix.ts
  • packages/timeline-state-resolver-types/src/integrations/sofieChef.ts
  • packages/timeline-state-resolver-types/src/integrations/panasonicPTZ.ts
  • packages/timeline-state-resolver-types/src/integrations/osc.ts
  • packages/timeline-state-resolver-types/src/integrations/obs.ts
  • packages/timeline-state-resolver-types/src/templateString.ts
  • packages/timeline-state-resolver/src/integrations/atem/tests/atem.spec.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/messages.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/tests/hyperdeck.spec.ts
  • packages/timeline-state-resolver/src/integrations/casparCG/index.ts
  • packages/timeline-state-resolver-types/src/integrations/quantel.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/tests/httpwatcher.spec.ts

Comment on lines +208 to +215
const deviceName = 'SofieChef'

if (!this.connected) {
statusCode = StatusCode.BAD
messages.push('Not connected')
statusDetails.push(
createSofieChefStatusDetail(SofieChefStatusCode.NOT_CONNECTED, {
deviceName,
})
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

Use runtime device name instead of a hardcoded literal.

Line 208 uses 'SofieChef', which can mislabel status context when the configured device name differs.

🔧 Proposed fix
-		const deviceName = 'SofieChef'
+		const deviceName = this.context.deviceName
📝 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
const deviceName = 'SofieChef'
if (!this.connected) {
statusCode = StatusCode.BAD
messages.push('Not connected')
statusDetails.push(
createSofieChefStatusDetail(SofieChefStatusCode.NOT_CONNECTED, {
deviceName,
})
const deviceName = this.context.deviceName
if (!this.connected) {
statusCode = StatusCode.BAD
statusDetails.push(
createSofieChefStatusDetail(SofieChefStatusCode.NOT_CONNECTED, {
deviceName,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/integrations/sofieChef/index.ts` around
lines 208 - 215, The status block currently hardcodes deviceName as 'SofieChef';
change it to use the runtime/configured device name (e.g. the instance property
or config field used by this integration) so status messages reflect the actual
device. Locate the block around the connected check (references: this.connected,
statusCode, statusDetails, createSofieChefStatusDetail,
SofieChefStatusCode.NOT_CONNECTED) and replace the literal deviceName with the
appropriate runtime value (for example this.deviceName or the config's
deviceName property) so the object passed to createSofieChefStatusDetail
contains the real device name.

Comment on lines +31 to +45
function normaliseDeviceStatus(input: DeviceStatusInput, active: boolean): DeviceStatus {
if ('statusDetails' in input) {
return {
statusCode: input.statusCode,
messages: input.statusDetails.map((d) => d.message),
statusDetails: input.statusDetails,
active,
}
}
return {
statusCode: input.statusCode,
messages: input.messages,
statusDetails: input.messages.map((message) => ({ message })),
active,
}
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

Keep statusDetails undefined for legacy devices.

The fallback branch currently fabricates statusDetails from messages, so callers can no longer distinguish legacy devices from integrations that actually support structured details. That breaks the feature-detection contract at both getStatus() and connectionChanged().

Proposed fix
 function normaliseDeviceStatus(input: DeviceStatusInput, active: boolean): DeviceStatus {
 	if ('statusDetails' in input) {
 		return {
 			statusCode: input.statusCode,
 			messages: input.statusDetails.map((d) => d.message),
 			statusDetails: input.statusDetails,
 			active,
 		}
 	}
 	return {
 		statusCode: input.statusCode,
 		messages: input.messages,
-		statusDetails: input.messages.map((message) => ({ message })),
+		statusDetails: undefined,
 		active,
 	}
 }

Please also cover the undefined vs [] distinction in packages/timeline-state-resolver/src/service/__tests__/deviceInstance.spec.ts.

Based on learnings: In the DeviceStatus interface within timeline-state-resolver, treat errors as a feature-detection mechanism: errors: [] means structured errors are supported, while errors: undefined means structured errors are not supported and consumers should fall back to the plain text messages array. Use this to guide reviews of interfaces and serialization logic, ensuring code handles both cases and that tests cover empty-array vs undefined scenarios for backward compatibility.

📝 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
function normaliseDeviceStatus(input: DeviceStatusInput, active: boolean): DeviceStatus {
if ('statusDetails' in input) {
return {
statusCode: input.statusCode,
messages: input.statusDetails.map((d) => d.message),
statusDetails: input.statusDetails,
active,
}
}
return {
statusCode: input.statusCode,
messages: input.messages,
statusDetails: input.messages.map((message) => ({ message })),
active,
}
function normaliseDeviceStatus(input: DeviceStatusInput, active: boolean): DeviceStatus {
if ('statusDetails' in input) {
return {
statusCode: input.statusCode,
messages: input.statusDetails.map((d) => d.message),
statusDetails: input.statusDetails,
active,
}
}
return {
statusCode: input.statusCode,
messages: input.messages,
statusDetails: undefined,
active,
}
}
🤖 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
31 - 45, normaliseDeviceStatus currently fabricates statusDetails from messages
for legacy devices; change it so that when input lacks statusDetails you return
statusDetails: undefined (not an array) so callers can detect legacy
devices—update the function normaliseDeviceStatus to copy statusDetails when
present and leave statusDetails undefined otherwise, preserving messages and
active; also update tests in deviceInstance.spec.ts to add assertions for both
cases (statusDetails undefined vs statusDetails === [] for integrations that
support structured errors) to cover the empty-array vs undefined distinction and
ensure consumers fall back to messages when statusDetails is undefined.

rjmunro added 23 commits April 30, 2026 11:47
PanasonicPtzDevice, OscDevice, SofieChefDevice and TelemetricsDevice are
now exported consistently alongside the other device classes.
Adds the DeviceStatusDetail interface for structured status messages with
an optional typed status code and context. Adds DeviceStatusInput as a
union of the legacy messages[] format and the new statusDetails[] format,
allowing devices to adopt the new format incrementally.

DeviceContextAPI.deviceName is also added so devices can include their
name in generated status messages.
Simplifies the template interpolation to use replaceAll with a regex,
making the implementation more direct. Updates the signature to use
Record<string, any> instead of a plain object type. The args parameter
defaults to an empty object when undefined.
Adds normaliseDeviceStatus() which converts the DeviceStatusInput union
(legacy messages[] or new statusDetails[]) into a full DeviceStatus with
both fields populated. Conversion is applied at the two points where
status crosses into the conductor: DeviceInstance.getStatus() and the
connectionChanged handler in the device context API.

The abstract device base class and DeviceInstance are updated to reflect
the DeviceStatusInput return type from device implementations.
Updates getStatus() return type from Omit<DeviceStatus, 'active'> to
DeviceStatusInput for kairos, multiOsc, singularLive, tcpSend, udpSend,
viscaOverIP, ograf and httpSend. Also adds deviceName to the shared test
device context mock.
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch from 61ea489 to de8eb8b Compare April 30, 2026 11:59
@sonarqubecloud
Copy link
Copy Markdown

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: 2

♻️ Duplicate comments (3)
packages/timeline-state-resolver/src/service/DeviceInstance.ts (1)

30-46: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not fabricate statusDetails for legacy messages input.

The legacy branch currently creates synthetic details from messages, which removes the undefined vs [] feature-detection signal for downstream consumers.

Proposed fix
 	return {
 		statusCode: input.statusCode,
 		messages: input.messages,
-		statusDetails: input.messages.map((message) => ({ message })),
+		statusDetails: undefined,
 		active,
 	}

Based on learnings: In timeline-state-resolver status handling, statusDetails: [] means structured support exists, while statusDetails: undefined means consumers should fall back to plain messages.

🤖 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
30 - 46, The legacy branch of normaliseDeviceStatus is incorrectly fabricating
statusDetails from messages; update the function normaliseDeviceStatus so that
when DeviceStatusInput does not contain statusDetails you copy statusCode and
messages as-is but leave statusDetails as undefined (do not map messages into
statusDetails), ensuring the returned DeviceStatus preserves the distinction
between undefined and [] for downstream consumers; adjust any type signatures if
needed so DeviceStatus.statusDetails can be undefined.
packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts (1)

59-68: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid emitting raw response bodies and headers in status context.

The catch block attaches potentially sensitive and unbounded payloads (error.response.body, and in handleResponse the response.headers) into status details. This can:

  1. Leak sensitive headers (Authorization, Cookie, Set-Cookie)
  2. Include unbounded response bodies in status objects

Consider redacting sensitive headers and capping body size before attaching to context.

🔧 Suggested hardening
+const SENSITIVE_HEADERS = new Set(['authorization', 'cookie', 'set-cookie', 'proxy-authorization'])
+const MAX_BODY_PREVIEW = 2048
+
+function sanitizeHeaders(headers: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
+	if (!headers) return undefined
+	const out: Record<string, unknown> = {}
+	for (const [k, v] of Object.entries(headers)) {
+		out[k] = SENSITIVE_HEADERS.has(k.toLowerCase()) ? '[REDACTED]' : v
+	}
+	return out
+}
+
+function bodyPreview(body: unknown): string | undefined {
+	if (typeof body !== 'string') return undefined
+	return body.length > MAX_BODY_PREVIEW ? `${body.slice(0, MAX_BODY_PREVIEW)}…` : body
+}

Then use bodyPreview(error.response.body) and sanitizeHeaders(response.headers) in the status detail contexts.

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

In `@packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts`
around lines 59 - 68, The catch block and handleResponse are currently attaching
raw error.response.body and response.headers into the status context (via
createHTTPWatcherStatusDetail), which can leak sensitive headers and unbounded
bodies; update the code paths that build the context (the catch handler around
the HTTP request and the handleResponse code that uses response.headers) to
replace direct bodies with a capped preview using
bodyPreview(error.response.body) or bodyPreview(response.body) and to sanitize
headers using sanitizeHeaders(response.headers) (or error.response.headers)
before adding them to the context, then pass the sanitized headers and
bodyPreview into createHTTPWatcherStatusDetail instead of the raw values.
packages/timeline-state-resolver-types/src/integrations/websocketClient.ts (1)

42-45: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Optional context fields will render as literal "undefined" in messages.

The context fields uri, reason, error, and statusCode are all optional in WebSocketClientStatusContextMap. When interpolateTemplateString encounters undefined values, JavaScript's string replacement will coerce them to the literal string "undefined", producing messages like:

  • "WS Disconnected: undefined (undefined)"
  • "WS Connection failed to undefined: undefined"
💡 Option A — Guard in the template helper (preferred, applies globally)

In interpolateTemplateString, coerce undefined values to empty string:

const safeContext = Object.fromEntries(
    Object.entries(context ?? {}).map(([k, v]) => [k, v ?? ''])
)
💡 Option B — Make templates resilient
-	[WebSocketClientStatusCode.NOT_CONNECTED]: 'WS Disconnected: {{uri}} ({{reason}})',
-	[WebSocketClientStatusCode.CONNECTION_FAILED]: 'WS Connection failed to {{uri}}: {{error}}',
+	[WebSocketClientStatusCode.NOT_CONNECTED]: 'WS Disconnected',
+	[WebSocketClientStatusCode.CONNECTION_FAILED]: 'WS Connection failed',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver-types/src/integrations/websocketClient.ts`
around lines 42 - 45, The status message templates render literal "undefined"
when optional context fields are missing; update the template interpolation
helper (interpolateTemplateString) to coerce undefined/null context values to an
empty string before replacement (e.g., build a safeContext from
WebSocketClientStatusContextMap entries mapping v => v ?? ''), so
WebSocketClientStatusMessages like 'WS Disconnected: {{uri}} ({{reason}})' and
'WS Connection failed to {{uri}}: {{error}}' produce clean output when fields
are absent.
🧹 Nitpick comments (1)
packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts (1)

258-336: ⚡ Quick win

Refactor getStatus() to reduce cognitive complexity (Sonar failure).

Line 258 currently packs connection, recording-time, slot, and transport checks in one method; splitting these into small helpers should bring complexity below the threshold and improve maintainability.

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

In `@packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts` around
lines 258 - 336, Split getStatus() into small helper methods: e.g., private
checkConnectionStatus(): {statusCode: StatusCode, details:
DeviceStatusDetail[]}, private checkRecordingTimeStatus(), private
checkSlotStatus(), and private checkTransportStatus(); each should inspect the
same fields used today (_connected, _initOptions, _minRecordingTime,
_recordingTime, _slotCount, _slotStatus, _suppressEmptySlotWarnings,
_expectedTransportStatus, _transportStatus) and return a statusCode plus
statusDetails created with createHyperdeckStatusDetail/HypedeckStatusCode as
currently used; then simplify getStatus() to call these helpers, merge their
statusDetails arrays, and compute the overall statusCode by taking the
highest-severity code (e.g., max value) among GOOD, WARNING_MINOR/MAJOR, BAD to
preserve current logic.
🤖 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/src/integrations/telemetrics/index.ts`:
- Line 53: The status context currently hardcodes deviceName as 'Telemetrics'
(variable deviceName) which breaks when a device is renamed; update the
assignment in the Telemetrics integration to use the runtime value
this.context.deviceName (or fall back to a sensible default) so the
status/context reflects the actual configured device name for methods that
reference deviceName.

In `@packages/timeline-state-resolver/src/integrations/websocketClient/index.ts`:
- Around line 77-79: getStatus() currently returns StatusCode.BAD with an empty
statusDetails array when disconnected, which normalizes to no human-readable
messages; update the fallback in getStatus (the method named getStatus in the
websocket client) to include a statusDetails entry with a clear message (e.g.,
"disconnected" or "no connection") so consumers receive a readable reason when
connection?.connectionStatus() is null/undefined instead of an empty messages
array.

---

Duplicate comments:
In `@packages/timeline-state-resolver-types/src/integrations/websocketClient.ts`:
- Around line 42-45: The status message templates render literal "undefined"
when optional context fields are missing; update the template interpolation
helper (interpolateTemplateString) to coerce undefined/null context values to an
empty string before replacement (e.g., build a safeContext from
WebSocketClientStatusContextMap entries mapping v => v ?? ''), so
WebSocketClientStatusMessages like 'WS Disconnected: {{uri}} ({{reason}})' and
'WS Connection failed to {{uri}}: {{error}}' produce clean output when fields
are absent.

In `@packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts`:
- Around line 59-68: The catch block and handleResponse are currently attaching
raw error.response.body and response.headers into the status context (via
createHTTPWatcherStatusDetail), which can leak sensitive headers and unbounded
bodies; update the code paths that build the context (the catch handler around
the HTTP request and the handleResponse code that uses response.headers) to
replace direct bodies with a capped preview using
bodyPreview(error.response.body) or bodyPreview(response.body) and to sanitize
headers using sanitizeHeaders(response.headers) (or error.response.headers)
before adding them to the context, then pass the sanitized headers and
bodyPreview into createHTTPWatcherStatusDetail instead of the raw values.

In `@packages/timeline-state-resolver/src/service/DeviceInstance.ts`:
- Around line 30-46: The legacy branch of normaliseDeviceStatus is incorrectly
fabricating statusDetails from messages; update the function
normaliseDeviceStatus so that when DeviceStatusInput does not contain
statusDetails you copy statusCode and messages as-is but leave statusDetails as
undefined (do not map messages into statusDetails), ensuring the returned
DeviceStatus preserves the distinction between undefined and [] for downstream
consumers; adjust any type signatures if needed so DeviceStatus.statusDetails
can be undefined.

---

Nitpick comments:
In `@packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts`:
- Around line 258-336: Split getStatus() into small helper methods: e.g.,
private checkConnectionStatus(): {statusCode: StatusCode, details:
DeviceStatusDetail[]}, private checkRecordingTimeStatus(), private
checkSlotStatus(), and private checkTransportStatus(); each should inspect the
same fields used today (_connected, _initOptions, _minRecordingTime,
_recordingTime, _slotCount, _slotStatus, _suppressEmptySlotWarnings,
_expectedTransportStatus, _transportStatus) and return a statusCode plus
statusDetails created with createHyperdeckStatusDetail/HypedeckStatusCode as
currently used; then simplify getStatus() to call these helpers, merge their
statusDetails arrays, and compute the overall statusCode by taking the
highest-severity code (e.g., max value) among GOOD, WARNING_MINOR/MAJOR, BAD to
preserve current logic.
🪄 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: 78ddcbb6-3974-4914-a78e-3234c82be44d

📥 Commits

Reviewing files that changed from the base of the PR and between 61ea489 and de8eb8b.

⛔ Files ignored due to path filters (2)
  • packages/timeline-state-resolver-types/src/__tests__/__snapshots__/index.spec.ts.snap is excluded by !**/*.snap
  • packages/timeline-state-resolver/src/__tests__/__snapshots__/index.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (82)
  • packages/timeline-state-resolver-api/src/device.ts
  • packages/timeline-state-resolver-api/src/tsr-api.ts
  • packages/timeline-state-resolver-types/src/__tests__/templateString.spec.ts
  • packages/timeline-state-resolver-types/src/device.ts
  • packages/timeline-state-resolver-types/src/deviceStatusDetail.ts
  • packages/timeline-state-resolver-types/src/index.ts
  • packages/timeline-state-resolver-types/src/integrations/atem.ts
  • packages/timeline-state-resolver-types/src/integrations/casparcg.ts
  • packages/timeline-state-resolver-types/src/integrations/httpWatcher.ts
  • packages/timeline-state-resolver-types/src/integrations/hyperdeck.ts
  • packages/timeline-state-resolver-types/src/integrations/lawo.ts
  • packages/timeline-state-resolver-types/src/integrations/obs.ts
  • packages/timeline-state-resolver-types/src/integrations/osc.ts
  • packages/timeline-state-resolver-types/src/integrations/panasonicPTZ.ts
  • packages/timeline-state-resolver-types/src/integrations/pharos.ts
  • packages/timeline-state-resolver-types/src/integrations/quantel.ts
  • packages/timeline-state-resolver-types/src/integrations/shotoku.ts
  • packages/timeline-state-resolver-types/src/integrations/sisyfos.ts
  • packages/timeline-state-resolver-types/src/integrations/sofieChef.ts
  • packages/timeline-state-resolver-types/src/integrations/telemetrics.ts
  • packages/timeline-state-resolver-types/src/integrations/tricaster.ts
  • packages/timeline-state-resolver-types/src/integrations/vizMSE.ts
  • packages/timeline-state-resolver-types/src/integrations/vmix.ts
  • packages/timeline-state-resolver-types/src/integrations/websocketClient.ts
  • packages/timeline-state-resolver-types/src/templateString.ts
  • packages/timeline-state-resolver/src/devices/device.ts
  • packages/timeline-state-resolver/src/index.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/__tests__/atem.spec.ts
  • packages/timeline-state-resolver/src/integrations/atem/index.ts
  • packages/timeline-state-resolver/src/integrations/atem/messages.ts
  • packages/timeline-state-resolver/src/integrations/casparCG/index.ts
  • packages/timeline-state-resolver/src/integrations/casparCG/messages.ts
  • packages/timeline-state-resolver/src/integrations/httpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/__tests__/httpwatcher.spec.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/index.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/messages.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/__tests__/hyperdeck.spec.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/index.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/messages.ts
  • packages/timeline-state-resolver/src/integrations/kairos/index.ts
  • packages/timeline-state-resolver/src/integrations/lawo/index.ts
  • packages/timeline-state-resolver/src/integrations/lawo/messages.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/obs/messages.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/osc/messages.ts
  • packages/timeline-state-resolver/src/integrations/panasonicPTZ/index.ts
  • packages/timeline-state-resolver/src/integrations/panasonicPTZ/messages.ts
  • packages/timeline-state-resolver/src/integrations/pharos/index.ts
  • packages/timeline-state-resolver/src/integrations/pharos/messages.ts
  • packages/timeline-state-resolver/src/integrations/quantel/index.ts
  • packages/timeline-state-resolver/src/integrations/quantel/messages.ts
  • packages/timeline-state-resolver/src/integrations/shotoku/index.ts
  • packages/timeline-state-resolver/src/integrations/shotoku/messages.ts
  • packages/timeline-state-resolver/src/integrations/singularLive/index.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/index.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/messages.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/__tests__/sofieChef.spec.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/index.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/messages.ts
  • packages/timeline-state-resolver/src/integrations/tcpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/__tests__/telemetrics.spec.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/index.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/messages.ts
  • packages/timeline-state-resolver/src/integrations/tricaster/index.ts
  • packages/timeline-state-resolver/src/integrations/tricaster/messages.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/vizMSE/index.ts
  • packages/timeline-state-resolver/src/integrations/vizMSE/messages.ts
  • packages/timeline-state-resolver/src/integrations/vmix/index.ts
  • packages/timeline-state-resolver/src/integrations/vmix/messages.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/__tests__/websocketClient.spec.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/connection.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/index.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/messages.ts
  • packages/timeline-state-resolver/src/service/DeviceInstance.ts
  • packages/timeline-state-resolver/src/service/__tests__/deviceInstance.spec.ts
✅ Files skipped from review due to trivial changes (18)
  • packages/timeline-state-resolver-types/src/index.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/messages.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/messages.ts
  • packages/timeline-state-resolver/src/index.ts
  • packages/timeline-state-resolver/src/integrations/singularLive/index.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/messages.ts
  • packages/timeline-state-resolver-types/src/integrations/httpWatcher.ts
  • packages/timeline-state-resolver-types/src/integrations/osc.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/messages.ts
  • packages/timeline-state-resolver-api/src/device.ts
  • packages/timeline-state-resolver-types/src/integrations/panasonicPTZ.ts
  • packages/timeline-state-resolver-types/src/integrations/pharos.ts
  • packages/timeline-state-resolver-types/src/integrations/lawo.ts
  • packages/timeline-state-resolver-types/src/integrations/shotoku.ts
  • packages/timeline-state-resolver/src/integrations/tests/testlib.ts
  • packages/timeline-state-resolver/src/devices/device.ts
  • packages/timeline-state-resolver-types/src/integrations/vizMSE.ts
  • packages/timeline-state-resolver/src/integrations/tcpSend/index.ts
🚧 Files skipped from review as they are similar to previous changes (37)
  • packages/timeline-state-resolver/src/service/tests/deviceInstance.spec.ts
  • packages/timeline-state-resolver/src/integrations/udpSend/index.ts
  • packages/timeline-state-resolver-api/src/tsr-api.ts
  • packages/timeline-state-resolver/src/integrations/atem/messages.ts
  • packages/timeline-state-resolver/src/integrations/vmix/messages.ts
  • packages/timeline-state-resolver/src/integrations/quantel/messages.ts
  • packages/timeline-state-resolver/src/integrations/kairos/index.ts
  • packages/timeline-state-resolver/src/integrations/vizMSE/messages.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/messages.ts
  • packages/timeline-state-resolver/src/integrations/osc/messages.ts
  • packages/timeline-state-resolver-types/src/tests/templateString.spec.ts
  • packages/timeline-state-resolver/src/integrations/lawo/messages.ts
  • packages/timeline-state-resolver/src/integrations/panasonicPTZ/messages.ts
  • packages/timeline-state-resolver/src/integrations/obs/messages.ts
  • packages/timeline-state-resolver/src/integrations/tricaster/messages.ts
  • packages/timeline-state-resolver/src/integrations/sofieChef/tests/sofieChef.spec.ts
  • packages/timeline-state-resolver/src/integrations/pharos/messages.ts
  • packages/timeline-state-resolver/src/integrations/casparCG/messages.ts
  • packages/timeline-state-resolver/src/integrations/abstract/index.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/messages.ts
  • packages/timeline-state-resolver-types/src/integrations/atem.ts
  • packages/timeline-state-resolver/src/integrations/panasonicPTZ/index.ts
  • packages/timeline-state-resolver/src/integrations/tricaster/index.ts
  • packages/timeline-state-resolver/src/integrations/osc/index.ts
  • packages/timeline-state-resolver-types/src/templateString.ts
  • packages/timeline-state-resolver-types/src/integrations/sofieChef.ts
  • packages/timeline-state-resolver/src/integrations/lawo/index.ts
  • packages/timeline-state-resolver/src/integrations/websocketClient/tests/websocketClient.spec.ts
  • packages/timeline-state-resolver/src/integrations/httpSend/index.ts
  • packages/timeline-state-resolver/src/integrations/telemetrics/tests/telemetrics.spec.ts
  • packages/timeline-state-resolver-types/src/integrations/obs.ts
  • packages/timeline-state-resolver/src/integrations/shotoku/messages.ts
  • packages/timeline-state-resolver/src/integrations/atem/tests/atem.spec.ts
  • packages/timeline-state-resolver-types/src/integrations/quantel.ts
  • packages/timeline-state-resolver/src/integrations/hyperdeck/tests/hyperdeck.spec.ts
  • packages/timeline-state-resolver/src/integrations/sisyfos/index.ts
  • packages/timeline-state-resolver/src/integrations/httpWatcher/tests/httpwatcher.spec.ts

const messages: string[] = []
getStatus(): DeviceStatusInput {
const statusDetails: DeviceStatusDetail[] = []
const deviceName = 'Telemetrics'
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 | ⚡ Quick win

Use runtime device name in Telemetrics status context.

Line 53 hardcodes 'Telemetrics', which can produce incorrect context when the device is renamed/configured differently. Use this.context.deviceName instead.

Suggested fix
-		const deviceName = 'Telemetrics'
+		const deviceName = this.context.deviceName
📝 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
const deviceName = 'Telemetrics'
const deviceName = this.context.deviceName
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/integrations/telemetrics/index.ts` at
line 53, The status context currently hardcodes deviceName as 'Telemetrics'
(variable deviceName) which breaks when a device is renamed; update the
assignment in the Telemetrics integration to use the runtime value
this.context.deviceName (or fall back to a sensible default) so the
status/context reflects the actual configured device name for methods that
reference deviceName.

Comment on lines +77 to 79
public getStatus(): DeviceStatusInput {
return this.connection?.connectionStatus() ?? { statusCode: StatusCode.BAD, statusDetails: [] }
}
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 | ⚡ Quick win

Restore a human-readable fallback for disconnected status.

StatusCode.BAD with statusDetails: [] normalizes to empty messages[], so consumers get no reason text. Please keep a fallback message in this branch.

Proposed fix
-		return this.connection?.connectionStatus() ?? { statusCode: StatusCode.BAD, statusDetails: [] }
+		return this.connection?.connectionStatus() ?? {
+			statusCode: StatusCode.BAD,
+			statusDetails: [{ message: 'No Connection' }],
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/timeline-state-resolver/src/integrations/websocketClient/index.ts`
around lines 77 - 79, getStatus() currently returns StatusCode.BAD with an empty
statusDetails array when disconnected, which normalizes to no human-readable
messages; update the fallback in getStatus (the method named getStatus in the
websocket client) to include a statusDetails entry with a clear message (e.g.,
"disconnected" or "no connection") so consumers receive a readable reason when
connection?.connectionStatus() is null/undefined instead of an empty messages
array.

@justandras
Copy link
Copy Markdown
Member

I've read through the PR, nothing jumps out to me, however I'm not too deep into TSR yet.

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.

6 participants