From e654dc2b19d04c6130b073ca9b14a5a44cee7724 Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Mon, 24 Aug 2026 17:38:01 +0100 Subject: [PATCH 01/11] feat(!): enable message identifiers as default --- .../src/plugin/__tests__/settings.test.ts | 13 ++--- .../plugin/actions/__tests__/action.test.ts | 20 +++---- .../plugin/src/plugin/actions/action-base.ts | 3 +- packages/plugin/src/plugin/actions/config.ts | 11 +++- packages/plugin/src/plugin/actions/service.ts | 4 +- .../src/plugin/actions/singleton-action.ts | 2 +- packages/plugin/src/plugin/index.ts | 3 +- packages/plugin/src/plugin/settings.ts | 53 ++++++++++++------- 8 files changed, 62 insertions(+), 47 deletions(-) diff --git a/packages/plugin/src/plugin/__tests__/settings.test.ts b/packages/plugin/src/plugin/__tests__/settings.test.ts index 98a46eba..eec7c110 100644 --- a/packages/plugin/src/plugin/__tests__/settings.test.ts +++ b/packages/plugin/src/plugin/__tests__/settings.test.ts @@ -8,6 +8,7 @@ import { type GetGlobalSettings, type SetGlobalSettings, } from "../../api/index.js"; +import type { Action } from "../actions/action.js"; import { actionStore } from "../actions/store.js"; import { connection } from "../connection.js"; import { Device } from "../devices/device.js"; @@ -97,8 +98,9 @@ describe("settings", () => { }); }); - describe("receiving emits with useExperimentalMessageIdentifiers set to false", () => { - beforeAll(() => (settings.useExperimentalMessageIdentifiers = false)); + describe("receiving emits with useLegacySettingsBehavior set to true", () => { + beforeAll(() => (settings.useLegacySettingsBehavior = true)); + afterAll(() => (settings.useLegacySettingsBehavior = false)); /** * Asserts {@link onDidReceiveGlobalSettings} is invoked when `didReceiveGlobalSettings` is emitted. @@ -170,7 +172,7 @@ describe("settings", () => { // Assert (emit). expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith<[DidReceiveSettingsEvent]>({ - action: actionStore.getActionById(ev.context)!, + action: actionStore.getActionById(ev.context)! as Action, payload: ev.payload, type: "didReceiveSettings", }); @@ -184,9 +186,8 @@ describe("settings", () => { }); }); - describe("receiving does not emit with useExperimentalMessageIdentifiers set to true", () => { - beforeAll(() => (settings.useExperimentalMessageIdentifiers = true)); - afterAll(() => (settings.useExperimentalMessageIdentifiers = false)); + describe("receiving does not emit with useLegacySettingsBehavior set to false", () => { + beforeAll(() => (settings.useLegacySettingsBehavior = false)); test("didReceiveGlobalSettings", () => { // Arrange. diff --git a/packages/plugin/src/plugin/actions/__tests__/action.test.ts b/packages/plugin/src/plugin/actions/__tests__/action.test.ts index 61ffb530..23330f07 100644 --- a/packages/plugin/src/plugin/actions/__tests__/action.test.ts +++ b/packages/plugin/src/plugin/actions/__tests__/action.test.ts @@ -53,7 +53,6 @@ describe("Action", () => { beforeAll(() => vi.spyOn(deviceStore, "getDeviceById").mockReturnValue(device)); afterEach(() => { - actionConfig.useExperimentalMessageIdentifiers = false; settingsCache.delete(source.context); vi.clearAllMocks(); }); @@ -75,14 +74,8 @@ describe("Action", () => { expect(deviceStore.getDeviceById).toHaveBeenLastCalledWith(source.device); }); - describe("with useExperimentalMessageIdentifiers set to true", () => { - beforeEach(() => { - actionConfig.useExperimentalMessageIdentifiers = true; - }); - - afterAll(() => { - actionConfig.useExperimentalMessageIdentifiers = false; - }); + describe("with useLegacySettingsBehavior set to false", () => { + beforeEach(() => (actionConfig.useLegacySettingsBehavior = false)); /** * Asserts {@link ActionBase.getSettings} returns cached settings when the cache is valid. @@ -112,10 +105,9 @@ describe("Action", () => { }); }); - describe("with useExperimentalMessageIdentifiers set to false", () => { - beforeAll(() => { - actionConfig.useExperimentalMessageIdentifiers = false; - }); + describe("with useLegacySettingsBehavior set to true", () => { + beforeAll(() => (actionConfig.useLegacySettingsBehavior = true)); + afterAll(() => (actionConfig.useLegacySettingsBehavior = false)); /** * Asserts {@link ActionBase.getSettings} ignores cached settings when experimental message identifiers are disabled. @@ -292,7 +284,7 @@ describe("Action", () => { describe("sending", () => { let action!: KeyAction; - beforeAll(() => (action = new KeyAction(source))); + beforeAll(() => (action = new KeyAction(source as WillAppear))); /** * Asserts {@link ActionBase.setSettings} invalidates the settings cache. diff --git a/packages/plugin/src/plugin/actions/action-base.ts b/packages/plugin/src/plugin/actions/action-base.ts index 2de89a6d..f7519d96 100644 --- a/packages/plugin/src/plugin/actions/action-base.ts +++ b/packages/plugin/src/plugin/actions/action-base.ts @@ -45,7 +45,7 @@ export class ActionBase extends ActionContext { * @returns Promise containing the action instance's settings. */ public async getSettings(): Promise { - if (actionConfig.useExperimentalMessageIdentifiers) { + if (!actionConfig.useLegacySettingsBehavior) { const cached = settingsCache.get(this.id); if (cached !== undefined) { logger.trace( @@ -56,6 +56,7 @@ export class ActionBase extends ActionContext { settings: cached, }), ); + return cached as TSettings; } } diff --git a/packages/plugin/src/plugin/actions/config.ts b/packages/plugin/src/plugin/actions/config.ts index 7e175999..4ad0b0ef 100644 --- a/packages/plugin/src/plugin/actions/config.ts +++ b/packages/plugin/src/plugin/actions/config.ts @@ -3,7 +3,14 @@ */ export const actionConfig = { /** - * Determines whether settings requests should use message identifiers and action settings cache behavior. + * Determines the behavior of when `onDidReceiveSettings` is fired. + * + * - `false` (default) — `onDidReceiveSettings` is only fired after the settings were updated + * within the property inspector. + * - `true` — `onDidReceiveSettings` is fired after the settings were updated within the property + * inspector, and after calling `action.getSettings()`. + * + * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ - useExperimentalMessageIdentifiers: false, + useLegacySettingsBehavior: false, }; diff --git a/packages/plugin/src/plugin/actions/service.ts b/packages/plugin/src/plugin/actions/service.ts index b8df0168..b07fa946 100644 --- a/packages/plugin/src/plugin/actions/service.ts +++ b/packages/plugin/src/plugin/actions/service.ts @@ -57,14 +57,14 @@ class ActionService extends ReadOnlyActionStore { const action = this.#createAction(ev); actionStore.set(action); - if (actionConfig.useExperimentalMessageIdentifiers) { + if (!actionConfig.useLegacySettingsBehavior) { settingsCache.set(ev.context, ev.payload.settings); } }); // Update the settings cache when settings are received. connection.prependListener("didReceiveSettings", (ev) => { - if (actionConfig.useExperimentalMessageIdentifiers) { + if (!actionConfig.useLegacySettingsBehavior) { settingsCache.set(ev.context, ev.payload.settings); } }); diff --git a/packages/plugin/src/plugin/actions/singleton-action.ts b/packages/plugin/src/plugin/actions/singleton-action.ts index 48c76a0e..dea5a0f1 100644 --- a/packages/plugin/src/plugin/actions/singleton-action.ts +++ b/packages/plugin/src/plugin/actions/singleton-action.ts @@ -68,7 +68,7 @@ export class SingletonAction { public onDidReceiveResources?(ev: DidReceiveResourcesEvent): Promise | void; /** - * Occurs when the settings associated with an action instance are requested using {@link ActionBase.getSettings}, or when the settings were updated by the property inspector. + * Occurs when the settings were updated within the property inspector. * @param ev Information about the event, including the source action and contextual payload information. */ public onDidReceiveSettings?(ev: DidReceiveSettingsEvent): Promise | void; diff --git a/packages/plugin/src/plugin/index.ts b/packages/plugin/src/plugin/index.ts index 4865779b..77f5e15b 100644 --- a/packages/plugin/src/plugin/index.ts +++ b/packages/plugin/src/plugin/index.ts @@ -8,7 +8,7 @@ import { deviceService, type DeviceService } from "./devices/service.js"; import { fileSystemLocaleProvider } from "./i18n.js"; import { logger } from "./logging/index.js"; import * as profiles from "./profiles.js"; -import { settings } from "./settings.js"; +import { settings, validateSettingsBehavior } from "./settings.js"; import * as system from "./system.js"; import { ui, type UIController } from "./ui.js"; @@ -116,6 +116,7 @@ export const streamDeck = { * @returns A promise resolved when a connection has been established. */ connect(): Promise { + validateSettingsBehavior(); return connection.connect(); }, }; diff --git a/packages/plugin/src/plugin/settings.ts b/packages/plugin/src/plugin/settings.ts index 48f6fa87..dfc44b41 100644 --- a/packages/plugin/src/plugin/settings.ts +++ b/packages/plugin/src/plugin/settings.ts @@ -12,27 +12,32 @@ import { requiresVersion } from "./validation.js"; export const settings = { /** - * Available from Stream Deck 7.1; determines whether message identifiers should be sent when getting - * action-instance or global settings. + * Determines the behavior of when `onDidReceiveSettings` is fired. * - * When `true`, the did-receive events associated with settings are only emitted when the action-instance - * or global settings are changed in the property inspector. - * @returns The value. + * - `false` (default) — `onDidReceiveSettings` is only fired after the settings were updated + * within the property inspector. + * - `true` — `onDidReceiveSettings` is fired after the settings were updated within the property + * inspector, and after calling `action.getSettings()`. + * + * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ - get useExperimentalMessageIdentifiers(): boolean { - return actionConfig.useExperimentalMessageIdentifiers; + get useLegacySettingsBehavior(): boolean { + return actionConfig.useLegacySettingsBehavior; }, /** - * Available from Stream Deck 7.1; determines whether message identifiers should be sent when getting - * action-instance or global settings. + * Determines the behavior of when `onDidReceiveSettings` is fired. + * + * - `false` (default) — `onDidReceiveSettings` is only fired after the settings were updated + * within the property inspector. + * - `true` — `onDidReceiveSettings` is fired after the settings were updated within the property + * inspector, and after calling `action.getSettings()`. * - * When `true`, the did-receive events associated with settings are only emitted when the action-instance - * or global settings are changed in the property inspector. + * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ - set useExperimentalMessageIdentifiers(value: boolean) { - requiresVersion(7.1, connection.version, "Message identifiers"); - actionConfig.useExperimentalMessageIdentifiers = value; + set useLegacySettingsBehavior(value: boolean) { + actionConfig.useLegacySettingsBehavior = value; + validateSettingsBehavior(); }, /** @@ -52,8 +57,7 @@ export const settings = { }, /** - * Occurs when the global settings are requested, or when the the global settings were updated in - * the property inspector. + * Occurs when the global settings were updated within the property inspector. * @template T The type of settings associated with the action. * @param listener Function to be invoked when the event occurs. * @returns A disposable that removes the listener. @@ -63,7 +67,7 @@ export const settings = { ): IDisposable => { return connection.disposableOn("didReceiveGlobalSettings", (ev: DidReceiveGlobalSettings) => { // Do nothing when the global settings were requested. - if (settings.useExperimentalMessageIdentifiers && ev.id) { + if (!settings.useLegacySettingsBehavior && ev.id) { return; } @@ -72,8 +76,7 @@ export const settings = { }, /** - * Occurs when the settings associated with an action instance are requested, or when the the settings - * were updated in the property inspector. + * Occurs when the settings, associated with an action, were updated within the property inspector. * @template T The type of settings associated with the action. * @param listener Function to be invoked when the event occurs. * @returns A disposable that removes the listener. @@ -83,7 +86,7 @@ export const settings = { ): IDisposable => { return connection.disposableOn("didReceiveSettings", (ev: DidReceiveSettings) => { // Do nothing when the action's settings were requested. - if (settings.useExperimentalMessageIdentifiers && ev.id) { + if (!settings.useLegacySettingsBehavior && ev.id) { return; } @@ -112,3 +115,13 @@ export const settings = { }); }, }; + +/** + * Validates the settings behavior is compatible with the Stream Deck version, and the minimum + * version defined within the manifest. + */ +export function validateSettingsBehavior(): never | void { + if (!settings.useLegacySettingsBehavior) { + requiresVersion(7.1, connection.version, "Recommended onDidReceiveSettings behavior"); + } +} From ac4ccf05a895d7a780b4ee68c8e6f0c1aa8a2b12 Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Mon, 24 Aug 2026 17:42:34 +0100 Subject: [PATCH 02/11] docs: add mention of global settings --- packages/plugin/src/plugin/actions/config.ts | 10 +++++----- packages/plugin/src/plugin/settings.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/plugin/src/plugin/actions/config.ts b/packages/plugin/src/plugin/actions/config.ts index 4ad0b0ef..404ec658 100644 --- a/packages/plugin/src/plugin/actions/config.ts +++ b/packages/plugin/src/plugin/actions/config.ts @@ -3,12 +3,12 @@ */ export const actionConfig = { /** - * Determines the behavior of when `onDidReceiveSettings` is fired. + * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` is fired. * - * - `false` (default) — `onDidReceiveSettings` is only fired after the settings were updated - * within the property inspector. - * - `true` — `onDidReceiveSettings` is fired after the settings were updated within the property - * inspector, and after calling `action.getSettings()`. + * - `false` (default) — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are only fired + * after the settings were updated within the property inspector. + * - `true` — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired after the settings + * were updated within the property inspector, and after calling `action.getSettings()`. * * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ diff --git a/packages/plugin/src/plugin/settings.ts b/packages/plugin/src/plugin/settings.ts index dfc44b41..b682ce5a 100644 --- a/packages/plugin/src/plugin/settings.ts +++ b/packages/plugin/src/plugin/settings.ts @@ -12,12 +12,12 @@ import { requiresVersion } from "./validation.js"; export const settings = { /** - * Determines the behavior of when `onDidReceiveSettings` is fired. + * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` is fired. * - * - `false` (default) — `onDidReceiveSettings` is only fired after the settings were updated - * within the property inspector. - * - `true` — `onDidReceiveSettings` is fired after the settings were updated within the property - * inspector, and after calling `action.getSettings()`. + * - `false` (default) — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are only fired + * after the settings were updated within the property inspector. + * - `true` — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired after the settings + * were updated within the property inspector, and after calling `action.getSettings()`. * * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ @@ -122,6 +122,6 @@ export const settings = { */ export function validateSettingsBehavior(): never | void { if (!settings.useLegacySettingsBehavior) { - requiresVersion(7.1, connection.version, "Recommended onDidReceiveSettings behavior"); + requiresVersion(7.1, connection.version, "Recommended onDidReceiveSettings/onDidReceiveGlobalSettings behavior"); } } From 890e4599fa21d5250beffb420fb41ce25477dffd Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Mon, 24 Aug 2026 17:46:16 +0100 Subject: [PATCH 03/11] docs: changelogs --- .changeset/cozy-maps-wait.md | 5 +++++ .changeset/gentle-parrots-teach.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/cozy-maps-wait.md create mode 100644 .changeset/gentle-parrots-teach.md diff --git a/.changeset/cozy-maps-wait.md b/.changeset/cozy-maps-wait.md new file mode 100644 index 00000000..3516133c --- /dev/null +++ b/.changeset/cozy-maps-wait.md @@ -0,0 +1,5 @@ +--- +"@elgato/streamdeck": major +--- + +Removed `useExperimentalMessageIdentifiers`, now enabled by default. diff --git a/.changeset/gentle-parrots-teach.md b/.changeset/gentle-parrots-teach.md new file mode 100644 index 00000000..73fadd2a --- /dev/null +++ b/.changeset/gentle-parrots-teach.md @@ -0,0 +1,5 @@ +--- +"@elgato/streamdeck": major +--- + +Updated `onDidReceiveSettings` and `onDidReceiveGlobalSettings` to only fire when settings are changed in the property inspector (requires Stream Deck 7.1 or higher). This behavior can be temporarily reverted by configuring `streamDeck.settings.useLegacySettingsBehavior` to be `true`. From ed6582741f906b5b95d0a0aed3aaf5a75ea036a5 Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Mon, 24 Aug 2026 17:53:02 +0100 Subject: [PATCH 04/11] test: fix types --- packages/plugin/src/plugin/__tests__/ui.test.ts | 7 ++++--- .../plugin/src/plugin/actions/__tests__/service.test.ts | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/plugin/src/plugin/__tests__/ui.test.ts b/packages/plugin/src/plugin/__tests__/ui.test.ts index 597df473..478905a6 100644 --- a/packages/plugin/src/plugin/__tests__/ui.test.ts +++ b/packages/plugin/src/plugin/__tests__/ui.test.ts @@ -8,6 +8,7 @@ import { type PropertyInspectorDidDisappear, type SendToPropertyInspector, } from "../../api/index.js"; +import type { Action } from "../actions/action.js"; import { KeyAction } from "../actions/key.js"; import { actionStore } from "../actions/store.js"; import { connection } from "../connection.js"; @@ -64,7 +65,7 @@ describe("UIController", () => { // Assert (emit). expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith<[PropertyInspectorDidAppearEvent]>({ - action: actionStore.getActionById(ev.context)!, + action: actionStore.getActionById(ev.context) as Action, type: "propertyInspectorDidAppear", }); @@ -96,7 +97,7 @@ describe("UIController", () => { // Assert (emit). expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith<[PropertyInspectorDidDisappearEvent]>({ - action: actionStore.getActionById(ev.context)!, + action: actionStore.getActionById(ev.context) as Action, type: "propertyInspectorDidDisappear", }); @@ -130,7 +131,7 @@ describe("UIController", () => { // Assert (emit). expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith<[SendToPluginEvent]>({ - action: actionStore.getActionById(ev.context)!, + action: actionStore.getActionById(ev.context) as Action, payload: { name: "Hello world", }, diff --git a/packages/plugin/src/plugin/actions/__tests__/service.test.ts b/packages/plugin/src/plugin/actions/__tests__/service.test.ts index b8e0efa8..79962fe9 100644 --- a/packages/plugin/src/plugin/actions/__tests__/service.test.ts +++ b/packages/plugin/src/plugin/actions/__tests__/service.test.ts @@ -73,7 +73,6 @@ describe("actions", () => { }); afterEach(() => { - actionConfig.useExperimentalMessageIdentifiers = false; vi.clearAllMocks(); }); @@ -581,7 +580,7 @@ describe("actions", () => { */ it("updates settings cache on willAppear/didReceiveSettings and clears on willDisappear", () => { // Arrange. - actionConfig.useExperimentalMessageIdentifiers = true; + actionConfig.useLegacySettingsBehavior = false; const context = "cache-lifecycle-context"; settingsCache.delete(context); From f17da624975719735bb25313fea0f16b9620b59a Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Mon, 24 Aug 2026 17:54:49 +0100 Subject: [PATCH 05/11] refactor: improve await logic of connect --- packages/plugin/src/plugin/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/src/plugin/index.ts b/packages/plugin/src/plugin/index.ts index 77f5e15b..de09571f 100644 --- a/packages/plugin/src/plugin/index.ts +++ b/packages/plugin/src/plugin/index.ts @@ -115,9 +115,9 @@ export const streamDeck = { * Connects the plugin to the Stream Deck. * @returns A promise resolved when a connection has been established. */ - connect(): Promise { + async connect(): Promise { validateSettingsBehavior(); - return connection.connect(); + await connection.connect(); }, }; From 36428d409770a60b37aeaabf094189b866d97933 Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Mon, 24 Aug 2026 17:55:26 +0100 Subject: [PATCH 06/11] docs: correct JSDocs --- packages/plugin/src/plugin/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/plugin/src/plugin/index.ts b/packages/plugin/src/plugin/index.ts index de09571f..74156410 100644 --- a/packages/plugin/src/plugin/index.ts +++ b/packages/plugin/src/plugin/index.ts @@ -113,7 +113,6 @@ export const streamDeck = { /** * Connects the plugin to the Stream Deck. - * @returns A promise resolved when a connection has been established. */ async connect(): Promise { validateSettingsBehavior(); From f1f8e97725739c9e5e49f342b7bd7b3173a88efe Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Mon, 24 Aug 2026 17:58:28 +0100 Subject: [PATCH 07/11] refactor: revert useLegacySettingsBehavior if setting fails --- packages/plugin/src/plugin/settings.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/plugin/src/plugin/settings.ts b/packages/plugin/src/plugin/settings.ts index b682ce5a..7ca25612 100644 --- a/packages/plugin/src/plugin/settings.ts +++ b/packages/plugin/src/plugin/settings.ts @@ -36,8 +36,15 @@ export const settings = { * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ set useLegacySettingsBehavior(value: boolean) { - actionConfig.useLegacySettingsBehavior = value; - validateSettingsBehavior(); + const prev = actionConfig.useLegacySettingsBehavior; + + try { + actionConfig.useLegacySettingsBehavior = value; + validateSettingsBehavior(); + } catch (err) { + actionConfig.useLegacySettingsBehavior = prev; + throw err; + } }, /** @@ -117,8 +124,8 @@ export const settings = { }; /** - * Validates the settings behavior is compatible with the Stream Deck version, and the minimum - * version defined within the manifest. + * Validates the current settings behavior is compatible with the Stream Deck version, and the + * minimum version defined within the manifest. */ export function validateSettingsBehavior(): never | void { if (!settings.useLegacySettingsBehavior) { From a3a97e4f3bf04d718b2bda87dfae98ccd9371740 Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Tue, 25 Aug 2026 13:06:09 +0100 Subject: [PATCH 08/11] chore: clear cache when settings behavior changes --- packages/plugin/src/plugin/actions/cache.ts | 7 +++++++ packages/plugin/src/plugin/settings.ts | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/packages/plugin/src/plugin/actions/cache.ts b/packages/plugin/src/plugin/actions/cache.ts index 9c80f9ef..7a588293 100644 --- a/packages/plugin/src/plugin/actions/cache.ts +++ b/packages/plugin/src/plugin/actions/cache.ts @@ -9,6 +9,13 @@ class SettingsCache { */ readonly #entries = new Map(); + /** + * Clears the cached settings. + */ + public clear(): void { + this.#entries.clear(); + } + /** * Removes the cached settings for the specified action. * @param id Action instance identifier. diff --git a/packages/plugin/src/plugin/settings.ts b/packages/plugin/src/plugin/settings.ts index 7ca25612..935c9c47 100644 --- a/packages/plugin/src/plugin/settings.ts +++ b/packages/plugin/src/plugin/settings.ts @@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto"; import type { DidReceiveGlobalSettings, DidReceiveSettings } from "../api/index.js"; import type { Action } from "./actions/action.js"; +import { settingsCache } from "./actions/cache.js"; import { actionConfig } from "./actions/config.js"; import { actionStore } from "./actions/store.js"; import { connection } from "./connection.js"; @@ -37,10 +38,15 @@ export const settings = { */ set useLegacySettingsBehavior(value: boolean) { const prev = actionConfig.useLegacySettingsBehavior; + if (prev === value) { + return; + } try { actionConfig.useLegacySettingsBehavior = value; validateSettingsBehavior(); + + settingsCache.clear(); } catch (err) { actionConfig.useLegacySettingsBehavior = prev; throw err; From 281250d44a5ae0001bc874d1bb602290fa2e1aca Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Tue, 25 Aug 2026 13:40:00 +0100 Subject: [PATCH 09/11] docs: fix docs --- packages/plugin/src/plugin/actions/config.ts | 3 ++- packages/plugin/src/plugin/settings.ts | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/plugin/src/plugin/actions/config.ts b/packages/plugin/src/plugin/actions/config.ts index 404ec658..c2640842 100644 --- a/packages/plugin/src/plugin/actions/config.ts +++ b/packages/plugin/src/plugin/actions/config.ts @@ -8,7 +8,8 @@ export const actionConfig = { * - `false` (default) — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are only fired * after the settings were updated within the property inspector. * - `true` — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired after the settings - * were updated within the property inspector, and after calling `action.getSettings()`. + * were updated within the property inspector, and after calling `action.getSettings()` and + * `streamDeck.settings.getGlobalSettings()` respectively. * * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ diff --git a/packages/plugin/src/plugin/settings.ts b/packages/plugin/src/plugin/settings.ts index 935c9c47..609250d0 100644 --- a/packages/plugin/src/plugin/settings.ts +++ b/packages/plugin/src/plugin/settings.ts @@ -18,7 +18,8 @@ export const settings = { * - `false` (default) — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are only fired * after the settings were updated within the property inspector. * - `true` — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired after the settings - * were updated within the property inspector, and after calling `action.getSettings()`. + * were updated within the property inspector, and after calling `action.getSettings()` and + * `streamDeck.settings.getGlobalSettings()` respectively. * * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ @@ -31,8 +32,9 @@ export const settings = { * * - `false` (default) — `onDidReceiveSettings` is only fired after the settings were updated * within the property inspector. - * - `true` — `onDidReceiveSettings` is fired after the settings were updated within the property - * inspector, and after calling `action.getSettings()`. + * - `true` — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired after the settings + * were updated within the property inspector, and after calling `action.getSettings()` and + * `streamDeck.settings.getGlobalSettings()` respectively. * * This option replaces `useExperimentalMessageIdentifiers`, with inverted behavior. */ From 284e17abf8b84ac7fd9a537bab8894f368ea0bae Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Tue, 25 Aug 2026 15:00:52 +0100 Subject: [PATCH 10/11] test: improve coverage of legacy behavior --- .../src/plugin/__tests__/settings.test.ts | 40 +++++++++++++ .../plugin/actions/__tests__/action.test.ts | 2 +- .../plugin/actions/__tests__/service.test.ts | 59 +++++++++++++++++++ .../src/plugin/actions/singleton-action.ts | 2 + packages/plugin/src/plugin/settings.ts | 6 +- 5 files changed, 105 insertions(+), 4 deletions(-) diff --git a/packages/plugin/src/plugin/__tests__/settings.test.ts b/packages/plugin/src/plugin/__tests__/settings.test.ts index eec7c110..ce058177 100644 --- a/packages/plugin/src/plugin/__tests__/settings.test.ts +++ b/packages/plugin/src/plugin/__tests__/settings.test.ts @@ -9,6 +9,7 @@ import { type SetGlobalSettings, } from "../../api/index.js"; import type { Action } from "../actions/action.js"; +import { settingsCache } from "../actions/cache.js"; import { actionStore } from "../actions/store.js"; import { connection } from "../connection.js"; import { Device } from "../devices/device.js"; @@ -98,6 +99,45 @@ describe("settings", () => { }); }); + describe("useLegacySettingsBehavior", () => { + /** + * Asserts that switching useLegacySettingsBehavior clears the settings cache. + */ + it("clears settings cache when switching modes", () => { + // Arrange. + const testContext = "cache-clear-test-context"; + settings.useLegacySettingsBehavior = false; + settingsCache.set(testContext, { name: "Cached" }); + expect(settingsCache.get(testContext)).toBeDefined(); + + // Act. + settings.useLegacySettingsBehavior = true; + + // Assert. + expect(settingsCache.get(testContext)).toBeUndefined(); + + // Cleanup. + settings.useLegacySettingsBehavior = false; + }); + + /** + * Asserts that switching useLegacySettingsBehavior back to false also clears the cache. + */ + it("clears settings cache when switching from true to false", () => { + // Arrange. + const testContext = "cache-clear-test-context-2"; + settings.useLegacySettingsBehavior = true; + settingsCache.set(testContext, { name: "Cached" }); + expect(settingsCache.get(testContext)).toBeDefined(); + + // Act. + settings.useLegacySettingsBehavior = false; + + // Assert. + expect(settingsCache.get(testContext)).toBeUndefined(); + }); + }); + describe("receiving emits with useLegacySettingsBehavior set to true", () => { beforeAll(() => (settings.useLegacySettingsBehavior = true)); afterAll(() => (settings.useLegacySettingsBehavior = false)); diff --git a/packages/plugin/src/plugin/actions/__tests__/action.test.ts b/packages/plugin/src/plugin/actions/__tests__/action.test.ts index 23330f07..436bd1a3 100644 --- a/packages/plugin/src/plugin/actions/__tests__/action.test.ts +++ b/packages/plugin/src/plugin/actions/__tests__/action.test.ts @@ -110,7 +110,7 @@ describe("Action", () => { afterAll(() => (actionConfig.useLegacySettingsBehavior = false)); /** - * Asserts {@link ActionBase.getSettings} ignores cached settings when experimental message identifiers are disabled. + * Asserts {@link ActionBase.getSettings} ignores cached settings when legacy settings behavior is enabled. */ it("getSettings ignores cached settings", async () => { // Arrange. diff --git a/packages/plugin/src/plugin/actions/__tests__/service.test.ts b/packages/plugin/src/plugin/actions/__tests__/service.test.ts index 79962fe9..b1e98283 100644 --- a/packages/plugin/src/plugin/actions/__tests__/service.test.ts +++ b/packages/plugin/src/plugin/actions/__tests__/service.test.ts @@ -653,6 +653,65 @@ describe("actions", () => { connection.emit("willDisappear", willDisappear); expect(settingsCache.get(context)).toBeUndefined(); }); + + /** + * Asserts settings cache is not updated when legacy settings behavior is enabled. + */ + it("does not update settings cache when useLegacySettingsBehavior is true", () => { + // Arrange. + actionConfig.useLegacySettingsBehavior = true; + const context = "cache-skip-context"; + settingsCache.delete(context); + + const willAppear = { + action: "com.elgato.test.key", + context, + device: "device123", + event: "willAppear", + payload: { + controller: "Keypad", + coordinates: { + column: 1, + row: 1, + }, + isInMultiAction: false, + resources: {}, + settings: { + name: "FromAppear", + }, + }, + } satisfies WillAppear; + + const didReceiveSettings = { + action: "com.elgato.test.key", + context, + device: "device123", + event: "didReceiveSettings", + payload: { + controller: "Keypad", + coordinates: { + column: 1, + row: 1, + }, + isInMultiAction: false, + resources: {}, + settings: { + name: "Updated", + }, + }, + } satisfies DidReceiveSettings; + + // Act, assert (cache should NOT be set on appear). + connection.emit("willAppear", willAppear); + expect(settingsCache.get(context)).toBeUndefined(); + + // Act, assert (cache should NOT be updated on settings event). + connection.emit("didReceiveSettings", didReceiveSettings); + expect(settingsCache.get(context)).toBeUndefined(); + + // Cleanup. + actionConfig.useLegacySettingsBehavior = false; + }); }); describe("registering an action", () => { diff --git a/packages/plugin/src/plugin/actions/singleton-action.ts b/packages/plugin/src/plugin/actions/singleton-action.ts index dea5a0f1..8f0c01cb 100644 --- a/packages/plugin/src/plugin/actions/singleton-action.ts +++ b/packages/plugin/src/plugin/actions/singleton-action.ts @@ -69,6 +69,8 @@ export class SingletonAction { /** * Occurs when the settings were updated within the property inspector. + * + * When `streamDeck.settings.useLegacySettingsBehavior` is `true`, also fires after calling `action.getSettings()`. * @param ev Information about the event, including the source action and contextual payload information. */ public onDidReceiveSettings?(ev: DidReceiveSettingsEvent): Promise | void; diff --git a/packages/plugin/src/plugin/settings.ts b/packages/plugin/src/plugin/settings.ts index 609250d0..811c960b 100644 --- a/packages/plugin/src/plugin/settings.ts +++ b/packages/plugin/src/plugin/settings.ts @@ -28,10 +28,10 @@ export const settings = { }, /** - * Determines the behavior of when `onDidReceiveSettings` is fired. + * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` is fired. * - * - `false` (default) — `onDidReceiveSettings` is only fired after the settings were updated - * within the property inspector. + * - `false` (default) — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are only fired + * after the settings were updated within the property inspector. * - `true` — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired after the settings * were updated within the property inspector, and after calling `action.getSettings()` and * `streamDeck.settings.getGlobalSettings()` respectively. From efd07c3d42f618878e562062169b2ee031f51b93 Mon Sep 17 00:00:00 2001 From: Richard Herman Date: Thu, 27 Aug 2026 12:43:32 +0100 Subject: [PATCH 11/11] docs: improve docs of events that are affected by useLegacySettingsBehavior --- packages/plugin/src/plugin/actions/config.ts | 2 +- .../plugin/src/plugin/actions/singleton-action.ts | 3 ++- packages/plugin/src/plugin/settings.ts | 12 +++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/plugin/src/plugin/actions/config.ts b/packages/plugin/src/plugin/actions/config.ts index c2640842..6cc0197c 100644 --- a/packages/plugin/src/plugin/actions/config.ts +++ b/packages/plugin/src/plugin/actions/config.ts @@ -3,7 +3,7 @@ */ export const actionConfig = { /** - * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` is fired. + * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired. * * - `false` (default) — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are only fired * after the settings were updated within the property inspector. diff --git a/packages/plugin/src/plugin/actions/singleton-action.ts b/packages/plugin/src/plugin/actions/singleton-action.ts index 8f0c01cb..f9e0d3ee 100644 --- a/packages/plugin/src/plugin/actions/singleton-action.ts +++ b/packages/plugin/src/plugin/actions/singleton-action.ts @@ -70,7 +70,8 @@ export class SingletonAction { /** * Occurs when the settings were updated within the property inspector. * - * When `streamDeck.settings.useLegacySettingsBehavior` is `true`, also fires after calling `action.getSettings()`. + * When `streamDeck.settings.useLegacySettingsBehavior` is set to `true`, this also fires after + * calling `getSettings()`. * @param ev Information about the event, including the source action and contextual payload information. */ public onDidReceiveSettings?(ev: DidReceiveSettingsEvent): Promise | void; diff --git a/packages/plugin/src/plugin/settings.ts b/packages/plugin/src/plugin/settings.ts index 811c960b..fb011695 100644 --- a/packages/plugin/src/plugin/settings.ts +++ b/packages/plugin/src/plugin/settings.ts @@ -13,7 +13,7 @@ import { requiresVersion } from "./validation.js"; export const settings = { /** - * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` is fired. + * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired. * * - `false` (default) — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are only fired * after the settings were updated within the property inspector. @@ -28,7 +28,7 @@ export const settings = { }, /** - * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` is fired. + * Determines the behavior of when `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are fired. * * - `false` (default) — `onDidReceiveSettings` and `onDidReceiveGlobalSettings` are only fired * after the settings were updated within the property inspector. @@ -73,6 +73,9 @@ export const settings = { /** * Occurs when the global settings were updated within the property inspector. + * + * When `streamDeck.settings.useLegacySettingsBehavior` is set to `true`, this event will also + * occur when calling `getGlobalSettings()`. * @template T The type of settings associated with the action. * @param listener Function to be invoked when the event occurs. * @returns A disposable that removes the listener. @@ -92,6 +95,9 @@ export const settings = { /** * Occurs when the settings, associated with an action, were updated within the property inspector. + * + * When `streamDeck.settings.useLegacySettingsBehavior` is set to `true`, this event will also + * occur when calling `getSettings()` on an action. * @template T The type of settings associated with the action. * @param listener Function to be invoked when the event occurs. * @returns A disposable that removes the listener. @@ -137,6 +143,6 @@ export const settings = { */ export function validateSettingsBehavior(): never | void { if (!settings.useLegacySettingsBehavior) { - requiresVersion(7.1, connection.version, "Recommended onDidReceiveSettings/onDidReceiveGlobalSettings behavior"); + requiresVersion(7.1, connection.version, "Default onDidReceiveSettings/onDidReceiveGlobalSettings behavior"); } }