Skip to content

feat: Error message customisation#1604

Draft
rjmunro wants to merge 8 commits intoSofie-Automation:mainfrom
bbc:rjmunro/error-message-customisation
Draft

feat: Error message customisation#1604
rjmunro wants to merge 8 commits intoSofie-Automation:mainfrom
bbc:rjmunro/error-message-customisation

Conversation

@rjmunro
Copy link
Contributor

@rjmunro rjmunro commented Jan 19, 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 (localization).

Sofie-Automation/sofie-timeline-state-resolver#418

New Behavior

Errors will include error codes and context arguments.

Errors can be overridden by blueprints that will be able to either supply strings to be interpolated (now supporting custom string codes easily), or a function to generate a message given strictly typed data. This allows for complex logic like pluralisation or unwrapping errors from HTTP responses of custom devices.

Testing

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

Affected areas

  • Blueprint API: Updated resolveErrorMessage signature for better type safety.
  • Error Resolution: Core logic updated to handle event-based error resolution.
  • Type Definitions: Added BlueprintErrorContexts for strict argument typing.

Time Frame

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

Other Information

This enables safer and richer error handling in blueprints without needing boilerplate type guards.

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

coderabbitai bot commented Jan 19, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

Walkthrough

This PR introduces a comprehensive error message customization system enabling blueprints to define custom messages for device, blueprint, and system errors. The changes span from new type definitions in shared libraries to integration points in the UI layer, allowing error messages to be resolved through a layered approach combining dynamic resolvers, blueprint-provided overrides, and defaults.

Changes

Cohort / File(s) Summary
Blueprint Error Type Definitions
packages/shared-lib/src/blueprintErrorMessages.ts, packages/shared-lib/src/systemErrorMessages.ts
New enums and interfaces defining error codes and their associated context structures for blueprint errors (5 codes) and system errors (3 codes) with contextual payloads.
Core Data Model Extension
packages/corelib/src/dataModel/Blueprint.ts
Extended Blueprint interface with three optional message-mapping fields: deviceErrorMessages, blueprintErrorMessages, and systemErrorMessages. Added type imports for BlueprintErrorCode and SystemErrorCode.
Error Resolution System
packages/corelib/src/ErrorMessageResolver.ts, packages/corelib/src/__tests__/ErrorMessageResolver.test.ts
Implemented ErrorMessageResolver class with layered message resolution (dynamic resolver → blueprint overrides → defaults). Comprehensive test suite covering device, blueprint, and system error handling with suppression and fallback logic.
Blueprint Integration API
packages/blueprints-integration/src/api/showStyle.ts
Extended ShowStyleBlueprintManifest interface with error message fields and resolveErrorMessage hook. Added new BlueprintErrorEvent discriminated union type and re-exported error codes/contexts.
Peripheral Device Errors
packages/shared-lib/src/peripheralDevice/peripheralDeviceAPI.ts
Re-exported DeviceStatusError from timeline-state-resolver-types and added optional errors array to PeripheralDeviceStatusObject for structured error data.
Meteor Server Blueprint Handling
meteor/server/api/blueprints/api.ts
Copy deviceErrorMessages, blueprintErrorMessages, and systemErrorMessages from blueprintManifest into newBlueprint object during SHOWSTYLE manifest processing.
Meteor Server Publication
meteor/server/publications/showStyleUI.ts
Enhanced showStyleUI publication to fetch and include blueprint.deviceErrorMessages in UIShowStyleBase when blueprintId is available. Added BlueprintFields projection for selective blueprint data retrieval.
UI Data Models
packages/meteor-lib/src/api/showStyles.ts, packages/webui/src/__mocks__/helpers/database.ts
Extended UIShowStyleBase with optional deviceErrorMessages property mapping TSR device error codes to messages with suppression support.
UI Component Integration
packages/webui/src/client/ui/RundownView.tsx, packages/webui/src/client/ui/RundownView/RundownNotifier.tsx
Pass showStyleBase to RundownNotifier component. Integrated ErrorMessageResolver to resolve device errors from structured error objects instead of plain messages, with fallback to existing message handling.
Library Exports
packages/corelib/src/index.ts
Added public exports for ErrorMessageResolver class and BlueprintForErrorMessages type.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

Contribution from BBC

Suggested reviewers

  • nytamin
  • jstarpl

Poem

🐰 A carrot-bundled change, so neat and grand,
With blueprints singing error songs so planned,
Device and system voices find their way,
Through resolvers layered, come what may,
Now errors speak in colors, bright and true! 🌈

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Error message customisation' clearly and concisely summarizes the main change: adding error message customization capability. It accurately reflects the primary feature being introduced across multiple files and modules.

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


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

❤️ Share

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

@rjmunro rjmunro changed the base branch from main to release53 January 19, 2026 13:27
@rjmunro
Copy link
Contributor Author

rjmunro commented Jan 19, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 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

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
meteor/server/publications/showStyleUI.ts (1)

87-107: Missing observer for Blueprint changes.

The publication fetches deviceErrorMessages from the Blueprint but doesn't observe the Blueprint collection for changes. If a blueprint's deviceErrorMessages are updated while the show style remains unchanged, clients won't receive the updated messages until the show style itself triggers an update.

Consider adding a Blueprint observer in setupUIShowStyleBasePublicationObservers:

♻️ Suggested addition to observers
 async function setupUIShowStyleBasePublicationObservers(
 	args: ReadonlyDeep<UIShowStyleBaseArgs>,
 	triggerUpdate: TriggerUpdate<UIShowStyleBaseUpdateProps>
 ): Promise<SetupObserversResult> {
+	const showStyleBase = await ShowStyleBases.findOneAsync(args.showStyleBaseId, {
+		projection: { blueprintId: 1 },
+	})
+
 	// Set up observers:
-	return [
+	const observers: SetupObserversResult = [
 		ShowStyleBases.observeChanges(
 			args.showStyleBaseId,
 			{
 				added: () => triggerUpdate({ invalidateShowStyle: true }),
 				changed: () => triggerUpdate({ invalidateShowStyle: true }),
 				removed: () => triggerUpdate({ invalidateShowStyle: true }),
 			},
 			{
 				projection: fieldSpecifier,
 			}
 		),
 	]
+
+	if (showStyleBase?.blueprintId) {
+		observers.push(
+			Blueprints.observeChanges(
+				showStyleBase.blueprintId,
+				{
+					changed: () => triggerUpdate({ invalidateShowStyle: true }),
+				},
+				{
+					projection: blueprintFieldSpecifier,
+				}
+			)
+		)
+	}
+
+	return observers
 }
🧹 Nitpick comments (3)
packages/blueprints-integration/src/api/showStyle.ts (1)

112-116: Consider adding documentation parity with deviceErrorMessages.

The deviceErrorMessages field has comprehensive JSDoc with examples, while blueprintErrorMessages and systemErrorMessages have minimal descriptions. For consistency and developer experience, consider adding similar detail about:

  • Available error codes (or where to find them)
  • Template interpolation syntax
  • Example usage
📝 Suggested documentation enhancement
-	/** Alternate blueprint error messages, to override the builtin ones produced by Sofie */
+	/**
+	 * Alternate blueprint error messages, to override the builtin ones produced by Sofie.
+	 * Keys are BlueprintErrorCode values (e.g., 'MISSING_SEGMENT_DATA', 'VALIDATION_ERROR').
+	 *
+	 * Templates use {{variable}} syntax for interpolation with context values.
+	 *
+	 * `@example`
+	 * ```typescript
+	 * blueprintErrorMessages: {
+	 *   [BlueprintErrorCode.VALIDATION_ERROR]: 'Config issue: {{reason}}',
+	 * }
+	 * ```
+	 */
 	blueprintErrorMessages?: Partial<Record<BlueprintErrorCode | string, string | undefined>>

-	/** Alternate system error messages, to override the builtin ones produced by Sofie */
+	/**
+	 * Alternate system error messages, to override the builtin ones produced by Sofie.
+	 * Keys are SystemErrorCode values (e.g., 'DATABASE_CONNECTION_LOST', 'SERVICE_UNAVAILABLE').
+	 *
+	 * Templates use {{variable}} syntax for interpolation with context values.
+	 *
+	 * `@example`
+	 * ```typescript
+	 * systemErrorMessages: {
+	 *   [SystemErrorCode.SERVICE_UNAVAILABLE]: '{{service}} is down: {{reason}}',
+	 * }
+	 * ```
+	 */
 	systemErrorMessages?: Partial<Record<SystemErrorCode | string, string | undefined>>
packages/corelib/src/ErrorMessageResolver.ts (2)

194-211: Inconsistent control flow compared to sibling methods.

The dynamic resolver check structure differs from getDeviceErrorMessage and getBlueprintErrorMessage. While functionally equivalent, this inconsistency reduces maintainability.

In other methods:

if (dynamicMessage !== undefined) {
  if (dynamicMessage === '') {
    return null
  }
  return ...
}

In this method, the checks are inverted (empty string checked first, then !== undefined).

♻️ Suggested refactor for consistency
 		// 1. Check dynamic resolver function (if available)
 		if (this.#resolverFunction) {
 			const dynamicMessage = this.#resolverFunction({
 				errorCode: errorCode as SystemErrorCode,
 				args,
 			} as BlueprintErrorEvent)

-			if (dynamicMessage === '') {
-				// Empty string means suppress the message
-				return null
-			}
-
 			if (dynamicMessage !== undefined) {
+				if (dynamicMessage === '') {
+					// Empty string means suppress the message
+					return null
+				}
 				// Resolver returned a custom message
 				return this.#blueprint
 					? wrapTranslatableMessageFromBlueprints({ key: dynamicMessage, args }, [this.#blueprint._id])
 					: { key: dynamicMessage, args }
 			}
 		}

189-192: Minor: Prefer unknown over any for type safety.

The args parameter uses any while the sibling methods use unknown. Using unknown is safer and maintains consistency.

🔧 Suggested fix
 	getSystemErrorMessage(
 		errorCode: SystemErrorCode | string,
-		args: { [k: string]: any }
+		args: { [k: string]: unknown }
 	): ITranslatableMessage | null {

@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch from 85e60cd to dc2efd1 Compare January 19, 2026 15:09
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch from cc8edb8 to d20435b Compare January 26, 2026 10:15
Add DeviceErrorContext, DeviceErrorMessageFunction, and deviceErrorMessages
to StudioBlueprintManifest for customizing TSR device error messages.

Add systemErrorMessages to SystemBlueprintManifest for customizing system
error messages.

Add ErrorMessageResolver class in corelib for runtime evaluation of
blueprint error message customizations (supports string templates and
functions).

Add DeviceStatusError type to PeripheralDeviceStatusObject for structured
error reporting from TSR devices.
When a peripheral device reports structured errors (status.errors), the
server now evaluates the Studio blueprint's deviceErrorMessages at runtime
to produce customized error messages.

This ensures blueprint customization happens at ingest time, not on the
client, providing consistent error messages across all clients.
Empty string will hide the message completely.
If it's a function, evaluate it then pass output along as if it were a
the output directly to start with, so handle undefined, empty strings
and context interpolation as normal.
New custom error messages should be self-contained

- Custom blueprint messages should be written appropriately
- Prefix Old TSR default messages earlier in the process for backwards
  compatibility
@rjmunro rjmunro force-pushed the rjmunro/error-message-customisation branch from d20435b to 288e4ef Compare January 28, 2026 14:19
@rjmunro
Copy link
Contributor Author

rjmunro commented Jan 28, 2026

Requires Sofie-Automation/sofie-timeline-state-resolver#418 to be merged first.

@PeterC89 PeterC89 changed the base branch from release53 to main February 4, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant