Skip to content

feat: vindral composer device#464

Open
Julusian wants to merge 23 commits into
Sofie-Automation:mainfrom
SuperFlyTV:feat/vindral-composer
Open

feat: vindral composer device#464
Julusian wants to merge 23 commits into
Sofie-Automation:mainfrom
SuperFlyTV:feat/vindral-composer

Conversation

@Julusian

@Julusian Julusian commented Jun 22, 2026

Copy link
Copy Markdown
Member

About the Contributor

This pull request is posted on behalf of SVT

Type of Contribution

This is a: Feature

New Behavior

Adds a new device integration for controlling Vindral Composer https://vindral.com/composer/

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

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR introduces a complete Vindral Composer device integration into timeline-state-resolver. It adds public TypeScript timeline types and JSON schemas, a state builder (buildVindralState), a command type system with a dispatcher (sendCommand), a full state differ (diffVindralStates) supporting dual media-player flows (direct HTTP and Script Engine), a VindralComposerDevice wrapper, service wiring, a Script Engine JS example, and comprehensive test suites for state building and state diffing.

Changes

Vindral Composer Integration

Layer / File(s) Summary
Public timeline types and JSON schemas
packages/timeline-state-resolver-types/src/integrations/vindral-composer/timeline.ts, packages/timeline-state-resolver-types/src/index.ts, packages/timeline-state-resolver/src/integrations/vindral-composer/$schemas/..., packages/timeline-state-resolver/src/integrations/vindral-composer/constants.ts
Adds TimelineContentTypeVindralComposer and VindralComposerPlaybackEndBehaviour enums, eight content interfaces, the TimelineContentVindralComposerAny union, and extends TimelineContentMap. JSON schemas define options (host, wsPort, httpPort, autoReconnect, useScriptEngine), mapping types (connector through switcher-overlay), and two action schemas (triggerConnector, executeScriptFunction). Adds TSR_SCRIPT_FN_MEDIA_PLAYER constant.
State builder interfaces and buildVindralState
packages/timeline-state-resolver/src/integrations/vindral-composer/stateBuilder.ts
Defines VindralComposerDeviceState and per-component state interfaces (VindralConnectorState, VindralSceneLayerState, VindralScriptEngineState, VindralSwitcherState, VindralSwitcherOverlayState, VindralMediaPlayerState, VindralHtmlState, VindralAudioSourceState). Implements buildVindralState with merge semantics, lookaheadOffset handling for media players, and null-explicit transition clearing for switchers.
State builder tests
packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/lib.ts, packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/*
Exports EMPTY_STATE and MAPPINGS test constants. Validates buildVindralState for all component types: ID/name selector fallback, multi-object merge, timelineObjIds aggregation, lookahead offset variants, and wrong-type filtering.
Command types and sendCommand dispatcher
packages/timeline-state-resolver/src/integrations/vindral-composer/commands.ts
Exports VindralCommandAny (discriminated union of eight command interfaces), VindralCommandWithContext, and sendCommand which routes each command variant to the corresponding VindralComposer connection method, choosing triggerConnector vs. triggerConnectorByValue by field presence.
diffVindralStates and per-component diff helpers
packages/timeline-state-resolver/src/integrations/vindral-composer/diffState.ts
Implements diffVindralStates orchestrating diffs for connectors, scene layers, script engines, switchers (cut/crossfade/direct-set), switcher overlays, media players (HTTP or Script Engine flow), HTML renderers (stop/set-url/start/reload ordering), and audio sources. Exports getDisabledScriptEngineWarnings and helper command builders.
State diff tests
packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/*
Covers all diff component types: basics, connectors, scene layers, script engines, switchers, switcher overlays, media players (HTTP and Script Engine flows), HTML renderers (all URL/running/reloadKey state transitions), audio sources, and disabled Script Engine warnings.
Script Engine JS example
packages/timeline-state-resolver/src/integrations/vindral-composer/SCRIPT_ENGINE.js.example
Adds a Vindral Composer Script Engine media-player reconciliation helper (tsrMediaPlayer, tsrMediaPlayerApply, tsrMediaPlayerApplyLoaded, tsrSetTimeCodeProperty, tsrMsToTimeCode) using a generation-stamped polling loop to load, seek, and play/pause inputs.
VindralComposerDevice wrapper and action handlers
packages/timeline-state-resolver/src/integrations/vindral-composer/index.ts, packages/timeline-state-resolver/src/integrations/vindral-composer/actions.ts
Implements VindralComposerDevice with init, terminate, connected, getStatus (BAD/WARNING_MAJOR/GOOD), convertTimelineStateToDeviceState, diffStates (with Script Engine warning tracking), and sendCommand. Implements getActions for triggerConnector and executeScriptFunction with validation and translated error results.
Service registration, manifest, and dev setup
packages/timeline-state-resolver/src/service/devices.ts, packages/timeline-state-resolver/src/service/ConnectionManager.ts, packages/timeline-state-resolver/src/manifest.ts, packages/timeline-state-resolver/package.json, packages/timeline-state-resolver/jest.config.js, packages/timeline-state-resolver/src/__mocks__/..., package.json
Registers VindralComposerDevice in DevicesDict and ImplementedServiceDeviceTypes, adds VINDRAL_COMPOSER to ConnectionManager's switch, wires the manifest entry, adds the vindral-composer-connection@^0.2.0 dependency, extends transformIgnorePatterns, adds the VindralComposer Jest mock class, and adds root dev/dev:quick-tsr scripts.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: vindral composer device' clearly and concisely summarizes the main change: adding a new device integration for Vindral Composer.
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.
Description check ✅ Passed The PR description clearly identifies this as a feature adding a new Vindral Composer device integration, which aligns with the comprehensive changeset across multiple files implementing this integration.

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

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

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (4)
packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/mediaPlayers.spec.ts (1)

51-75: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Assert the default playing: true branch when playing is omitted.

This test validates selector/source URL but does not lock in the default-playing behavior from buildVindralState.

Suggested assertion update
 		expect(result.mediaPlayers['mpLayer']).toMatchObject({
 			selector: { target: 'player-guid', targetName: 'ClipPlayer1' },
 			autoPlayOnMediaChange: true,
 			sourceUrl: 'http://cdn.example.com/other.mp4',
+			playing: true,
 		})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/mediaPlayers.spec.ts`
around lines 51 - 75, The test for media player mapping does not assert the
default playing behavior when the playing property is omitted from the input. In
the expect statement where result.mediaPlayers is checked with toMatchObject,
add an assertion for playing: true to the expected object to verify that the
default playing state is correctly applied by buildVindralState when no explicit
playing value is provided.
packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/switchers.spec.ts (1)

53-79: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add a regression test for transition: null clear behavior.

The suite currently validates transition: undefined but not the explicit clear path (null) that the builder handles specially.

Suggested test addition
 describe('stateBuilder — switchers', () => {
+	test('switcher mapping — explicit null transition clears transition', () => {
+		const result = buildVindralState(
+			{
+				time: 0,
+				objects: [
+					makeDeviceTimelineStateObject({
+						enable: { start: 0 },
+						id: 'obj0',
+						layer: 'swLayer',
+						content: {
+							deviceType: DeviceType.VINDRAL_COMPOSER,
+							type: TimelineContentTypeVindralComposer.SWITCHER,
+							switcher: { transition: null },
+						},
+					}),
+				],
+			},
+			MAPPINGS
+		)
+		expect(result.switchers['abc-123']).toMatchObject({ transition: null })
+	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/switchers.spec.ts`
around lines 53 - 79, Add a regression test case to validate the explicit null
clear path for the transition property in the switcher mapping. After the
existing test "switcher mapping — partial fields", create a new test that passes
transition: null in the switcher content and verify that the builder correctly
handles this explicit null value the same way it handles undefined. Include an
assertion in the buildVindralState result that confirms the transition property
is handled appropriately when explicitly set to null.
packages/timeline-state-resolver/src/__mocks__/vindral-composer-connection.ts (1)

6-6: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Keep removeAllListeners behavior aligned with EventEmitter in the mock.

Line 6 currently makes removeAllListeners a no-op, which can hide listener-lifecycle bugs in tests. Preserve base behavior and still track invocations.

Suggested fix
-	removeAllListeners = jest.fn()
+	removeAllListeners = jest.fn((event?: string | symbol) => {
+		super.removeAllListeners(event)
+		return this
+	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/timeline-state-resolver/src/__mocks__/vindral-composer-connection.ts`
at line 6, The `removeAllListeners` property in the mock is currently only a
no-op jest.fn() without actual implementation, which can mask listener-related
bugs in tests. Modify the `removeAllListeners` mock to preserve the actual
EventEmitter behavior by using jest.fn().mockImplementation() to call the parent
class's removeAllListeners method while still tracking invocations for test
assertions. This ensures listeners are actually removed during tests while
maintaining the ability to spy on and verify the method was called.
packages/timeline-state-resolver/src/integrations/vindral-composer/diffState.ts (1)

175-283: 🧹 Nitpick | 🔵 Trivial | 🏗️ Heavy lift

Split diffMediaPlayers into focused helpers before this grows further.

Line 175 currently combines script-engine flow, direct HTTP flow, and ordering rules in one high-complexity block. This is already tripping static complexity gates and makes sequencing regressions harder to catch.

♻️ Refactor outline
 function diffMediaPlayers(
   oldState: VindralComposerDeviceState,
   newState: VindralComposerDeviceState,
   useScriptEngine: boolean
 ): VindralCommandWithContext[] {
   const commands: VindralCommandWithContext[] = []

   for (const key of allKeys(oldState.mediaPlayers, newState.mediaPlayers)) {
     const old = oldState.mediaPlayers[key]
     const next = newState.mediaPlayers[key]
     if (!next) continue
-
-    // script + direct flow logic interleaved here
+    commands.push(
+      ...(useScriptEngine
+        ? diffMediaPlayerScriptEngine(old, next, newState.stateTime, key)
+        : diffMediaPlayerDirect(old, next, key))
+    )
   }

   return commands
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/diffState.ts`
around lines 175 - 283, The diffMediaPlayers function combines three distinct
concerns: script-engine flow logic, direct HTTP flow logic, and play/pause
command handling in a single 100+ line block. Extract the script-engine flow
handling (the logic inside the useScriptEngine conditional starting around the
scriptInTime variable) into a separate helper function that takes the old and
new media player states and returns an array of commands. Similarly, extract the
direct HTTP flow handling (the logic after the continue statement, handling
endBehaviour, autoPlayOnMediaChange, sourceUrl changes, and play/pause) into its
own helper function. Then refactor the main diffMediaPlayers loop to call these
focused helpers, passing the necessary parameters and collecting their returned
commands. This separation will reduce the cyclomatic complexity and make
sequencing logic easier to verify and maintain.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/timeline-state-resolver-types/src/integrations/vindral-composer/timeline.ts`:
- Around line 30-43: The connector property in the
TimelineContentVindralComposerConnector interface currently allows both name and
value to be optional, which violates the documented mutual exclusivity
constraint and allows invalid states where both fields or neither field are
present. Replace the optional name and value fields with a union type that
enforces compile-time exclusivity, such as a discriminated union pattern where
one variant contains only name with value excluded, and another variant contains
only value with name excluded. This ensures only one of the two fields can be
present and required at a time in the connector object.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/basics.spec.ts`:
- Around line 5-11: The test cases 'undefined old state → empty new state: no
commands' and 'empty → empty: no commands' are flagged as having no assertions
because the expect statements are wrapped inside the compareStates function
call. Add expect.hasAssertions() at the beginning of each test function body to
explicitly signal to the linter that assertions are present, which will suppress
the warning and keep the lint signal clean.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/`$schemas/actions.json:
- Around line 8-26: The triggerConnector action payload schema does not enforce
that name and value are mutually exclusive as documented. Add a oneOf constraint
to the payload object that defines two schemas: one requiring the name property
and one requiring the value property. In each schema variant, ensure params
remains optional and additionalProperties is set to false. This will enforce
that exactly one of name or value must be provided while still allowing the
optional params field in both cases.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/`$schemas/mappings.json:
- Around line 46-63: The switcher schema declares mutual exclusivity between
switcherId and switcherName in the descriptions, but does not enforce this
constraint in the JSON schema itself, allowing both, neither, or just one to be
present. Add a JSON Schema constraint using oneOf to enforce that exactly one of
the two fields (switcherId or switcherName) must be provided, eliminating the
non-deterministic targeting issue caused by ambiguous selector fallback
behavior.

In `@packages/timeline-state-resolver/src/integrations/vindral-composer/index.ts`:
- Around line 69-75: The terminate() method clears the _connection reference
before the disconnected callback can update state, leaving _connected as true
after shutdown, which causes connected and getStatus() to report incorrect
status. To fix this, explicitly set the _connected flag to false at the
beginning of the terminate() method before clearing the connection reference.
This ensures the connection state is immediately marked as disconnected and
prevents stale connected status from being reported after termination.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/stateBuilder.ts`:
- Around line 92-247: The buildVindralState function exceeds the cognitive
complexity limit of 30 (currently at 53) due to its large switch statement
handling multiple mapping types. Refactor by extracting each case block in the
switch statement (MappingVindralComposerType.Connector, SceneLayer,
ScriptEngine, Switcher, SwitcherOverlay, MediaPlayer, AudioSource, and Html)
into separate dedicated handler functions. These handlers should accept the
relevant parameters (obj, mapping, content, existing state if needed) and return
the constructed state object. Keep buildVindralState as a thin orchestration
function that iterates through objects, validates mapping and content types, and
dispatches to the appropriate handler function based on
mapping.options.mappingType.

---

Nitpick comments:
In
`@packages/timeline-state-resolver/src/__mocks__/vindral-composer-connection.ts`:
- Line 6: The `removeAllListeners` property in the mock is currently only a
no-op jest.fn() without actual implementation, which can mask listener-related
bugs in tests. Modify the `removeAllListeners` mock to preserve the actual
EventEmitter behavior by using jest.fn().mockImplementation() to call the parent
class's removeAllListeners method while still tracking invocations for test
assertions. This ensures listeners are actually removed during tests while
maintaining the ability to spy on and verify the method was called.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/mediaPlayers.spec.ts`:
- Around line 51-75: The test for media player mapping does not assert the
default playing behavior when the playing property is omitted from the input. In
the expect statement where result.mediaPlayers is checked with toMatchObject,
add an assertion for playing: true to the expected object to verify that the
default playing state is correctly applied by buildVindralState when no explicit
playing value is provided.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/switchers.spec.ts`:
- Around line 53-79: Add a regression test case to validate the explicit null
clear path for the transition property in the switcher mapping. After the
existing test "switcher mapping — partial fields", create a new test that passes
transition: null in the switcher content and verify that the builder correctly
handles this explicit null value the same way it handles undefined. Include an
assertion in the buildVindralState result that confirms the transition property
is handled appropriately when explicitly set to null.

In
`@packages/timeline-state-resolver/src/integrations/vindral-composer/diffState.ts`:
- Around line 175-283: The diffMediaPlayers function combines three distinct
concerns: script-engine flow logic, direct HTTP flow logic, and play/pause
command handling in a single 100+ line block. Extract the script-engine flow
handling (the logic inside the useScriptEngine conditional starting around the
scriptInTime variable) into a separate helper function that takes the old and
new media player states and returns an array of commands. Similarly, extract the
direct HTTP flow handling (the logic after the continue statement, handling
endBehaviour, autoPlayOnMediaChange, sourceUrl changes, and play/pause) into its
own helper function. Then refactor the main diffMediaPlayers loop to call these
focused helpers, passing the necessary parameters and collecting their returned
commands. This separation will reduce the cyclomatic complexity and make
sequencing logic easier to verify and maintain.
🪄 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: b4109a2a-82cb-4386-9131-6cec5fecb1cf

📥 Commits

Reviewing files that changed from the base of the PR and between d0d2986 and ef02a9c.

⛔ Files ignored due to path filters (6)
  • packages/timeline-state-resolver-types/src/__tests__/__snapshots__/index.spec.ts.snap is excluded by !**/*.snap
  • packages/timeline-state-resolver-types/src/generated/device-options.ts is excluded by !**/generated/**
  • packages/timeline-state-resolver-types/src/generated/index.ts is excluded by !**/generated/**
  • packages/timeline-state-resolver-types/src/generated/vindral-composer.ts is excluded by !**/generated/**
  • packages/timeline-state-resolver/src/__tests__/__snapshots__/index.spec.ts.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (39)
  • package.json
  • packages/timeline-state-resolver-types/src/index.ts
  • packages/timeline-state-resolver-types/src/integrations/vindral-composer/timeline.ts
  • packages/timeline-state-resolver/jest.config.js
  • packages/timeline-state-resolver/package.json
  • packages/timeline-state-resolver/src/__mocks__/_setup-all-mocks.ts
  • packages/timeline-state-resolver/src/__mocks__/vindral-composer-connection.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/$schemas/actions.json
  • packages/timeline-state-resolver/src/integrations/vindral-composer/$schemas/mappings.json
  • packages/timeline-state-resolver/src/integrations/vindral-composer/$schemas/options.json
  • packages/timeline-state-resolver/src/integrations/vindral-composer/SCRIPT_ENGINE.js.example
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/audioSources.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/basics.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/connectors.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/helpers.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/htmlRenderers.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/mediaPlayers.scriptEngine.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/mediaPlayers.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/sceneLayers.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/scriptEngineWarnings.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/scriptEngines.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/switcherOverlays.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/diffState/switchers.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/lib.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/audioSources.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/htmlRenderers.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/layers.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/mediaPlayers.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/switcherOverlays.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/__tests__/stateBuilder/switchers.spec.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/actions.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/commands.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/constants.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/diffState.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/index.ts
  • packages/timeline-state-resolver/src/integrations/vindral-composer/stateBuilder.ts
  • packages/timeline-state-resolver/src/manifest.ts
  • packages/timeline-state-resolver/src/service/ConnectionManager.ts
  • packages/timeline-state-resolver/src/service/devices.ts

Comment on lines +69 to +75
async terminate(): Promise<void> {
const conn = this._connection
this._connection = undefined
conn?.removeAllListeners()
conn?.on('error', () => undefined) // prevent unhandled error events during async disconnect
conn?.disconnect()
}

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

Reset connection flags during termination to avoid stale connected status.

Line 71 drops _connection and Line 72 removes listeners before any disconnected callback can update state, so _connected can remain true after terminate(). That leaves connected/getStatus() reporting incorrect status post-shutdown.

Suggested fix
 async terminate(): Promise<void> {
 	const conn = this._connection
 	this._connection = undefined
+	this._connected = false
+	this._stateWarnings = []
+	this._connectionChanged()
 	conn?.removeAllListeners()
 	conn?.on('error', () => undefined) // prevent unhandled error events during async disconnect
 	conn?.disconnect()
 }
📝 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
async terminate(): Promise<void> {
const conn = this._connection
this._connection = undefined
conn?.removeAllListeners()
conn?.on('error', () => undefined) // prevent unhandled error events during async disconnect
conn?.disconnect()
}
async terminate(): Promise<void> {
const conn = this._connection
this._connection = undefined
this._connected = false
this._stateWarnings = []
this._connectionChanged()
conn?.removeAllListeners()
conn?.on('error', () => undefined) // prevent unhandled error events during async disconnect
conn?.disconnect()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/timeline-state-resolver/src/integrations/vindral-composer/index.ts`
around lines 69 - 75, The terminate() method clears the _connection reference
before the disconnected callback can update state, leaving _connected as true
after shutdown, which causes connected and getStatus() to report incorrect
status. To fix this, explicitly set the _connected flag to false at the
beginning of the terminate() method before clearing the connection reference.
This ensures the connection state is immediately marked as disconnected and
prevents stale connected status from being reported after termination.

@sonarqubecloud

sonarqubecloud Bot commented Jun 22, 2026

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
10 New issues
0 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@justandras justandras self-requested a review June 22, 2026 15:26
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.

2 participants