Skip to content

fix(actions): store camera group selection per-action instead of global (#208)#209

Merged
niklam merged 2 commits into
masterfrom
fix/208-camera-group-per-instance
Mar 28, 2026
Merged

fix(actions): store camera group selection per-action instead of global (#208)#209
niklam merged 2 commits into
masterfrom
fix/208-camera-group-per-instance

Conversation

@niklam
Copy link
Copy Markdown
Owner

@niklam niklam commented Mar 28, 2026

Related Issue

Fixes #208

What changed?

Camera group subset selection was stored in global settings (cameraGroupSubset), causing all Camera Controls "Cycle Camera" instances to share the same camera group checkboxes. This PR moves the setting to per-action storage so each button maintains its own selection.

  • Added cameraGroupSubset to the per-action settings Zod schema
  • Extracted parseGroupSubset() for reusable parsing logic
  • Updated getEnabledGroupNames() to read from per-action settings first, falling back to the legacy global setting for existing users
  • Changed PI template from useGlobalSettings to useSettings
  • Renamed GLOBAL_KEYSETTING_KEY in PI and CAMERA_GROUPS_GLOBAL_KEYCAMERA_GROUPS_SETTING_KEY in action code

How to test

  1. Add two Camera Controls actions, both set to "Cycle Camera"
  2. Open PI for button A, select only "Cockpit" and "Chase"
  3. Open PI for button B — it should retain its own independent selection (defaults)
  4. Existing users with a saved global cameraGroupSubset should see their selection preserved until they make a per-button change

Checklist

  • Linked to an approved issue
  • New code has unit tests
  • Changes registered in all applicable plugins (Stream Deck, Mirabox)
  • No unrelated changes included

Summary by CodeRabbit

  • Enhancements
    • Per-action camera group configuration added; actions can now carry their own camera group selection.
    • Per-action settings take priority over legacy/global settings; system defaults remain as fallback.
  • Bug Fixes
    • Improved handling of missing, invalid, or explicitly-empty group selections to ensure sensible fallbacks.
  • Tests
    • Expanded test coverage for parsing, precedence, and disable-all scenarios.

…al (#208)

Camera group subset was stored in global settings, causing all Camera
Controls instances to share the same selection. Move to per-action
settings so each button maintains its own camera group list.

Falls back to legacy global setting for existing users who already
configured their groups.

Fixes #208
@github-actions github-actions Bot added the type: bug Bug fix label Mar 28, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

Migrates camera group subset storage from global settings to per-action settings, threads per-action cameraGroupSubset through execution and icon logic, adds parsing helpers, and updates the PI to seed per-action settings from legacy global values when absent. Tests updated to cover parsing and precedence behavior.

Changes

Cohort / File(s) Summary
Core Logic
packages/actions/src/actions/camera-controls.ts
Renamed CAMERA_GROUPS_GLOBAL_KEYCAMERA_GROUPS_SETTING_KEY; added cameraGroupSubset to CameraControlsSettings; introduced parseGroupSubset(raw) returning `string[]
Tests
packages/actions/src/actions/camera-controls.test.ts
Replaced constant usages; added parseGroupSubset test suite (undefined, invalid JSON, missing groups, valid object/string, explicit disable); refactored getEnabledGroupNames tests to assert per-action overrides and fallback behavior.
Property Inspector (PI)
packages/stream-deck-plugin/src/pi/camera-focus.ejs
Switched PI bindings from global to per-action settings hooks; register legacy global reader to seed per-action cameraGroupSubset when absent; continue rendering and save callback from settings hook.

Sequence Diagram(s)

sequenceDiagram
  participant PI as PI (camera-focus.ejs)
  participant Settings as Per-Action Settings
  participant Legacy as Global Settings
  participant Action as CameraControls (action)
  participant Engine as Execution / Icon Update

  PI->>Legacy: read legacy `cameraGroupSubset`
  PI->>Settings: useSettings() hook (per-action)
  Settings-->>PI: currentGroups (seeded from Legacy if absent)
  PI->>Settings: save updated `cameraGroupSubset`

  Action->>Settings: read per-action `cameraGroupSubset`
  Action->>Legacy: read legacy `cameraGroupSubset` (fallback)
  Action->>Action: parseGroupSubset(raw) / getEnabledGroupNames(raw)
  Action->>Engine: executeCycle(..., enabledGroups)
  Action->>Engine: updateCycleIcon(enabledGroups)
Loading

(Note: colored rectangles not needed for this simple sequence.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #174: Prior changes around camera-group subset parsing and per-action override logic that this PR extends/refines.
  • #184: Related changes that thread per-action camera-group settings into execution and icon-generation paths.

Poem

🐰 I hopped through code with twitchy nose,
each button now keeps its own camera pose.
Legacy seeds, new settings grow,
cycles spin where wildflowers blow. 📸

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main change: storing camera group selection per-action instead of global, which is the core fix for issue #208.
Description check ✅ Passed The PR description covers all required template sections: related issue, detailed what changed explanation, clear testing steps, and a complete checklist with all items marked as done.
Linked Issues check ✅ Passed The code changes fully implement the requirements from issue #208: moved cameraGroupSubset to per-action settings, updated PI to use useSettings, added parseGroupSubset logic, implemented backward compatibility fallback, and updated all affected files with tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #208: camera control files, PI template, and tests are appropriately modified for per-action storage migration; no unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/208-camera-group-per-instance

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/stream-deck-plugin/src/pi/camera-focus.ejs (1)

273-289: ⚠️ Potential issue | 🟠 Major

Keep the legacy global subset visible until an action-specific value exists.

When useSettings() returns no per-action value, this branch resets currentGroups to {} and the PI renders DEFAULT_ENABLED, but packages/actions/src/actions/camera-controls.ts still falls back to the legacy global cameraGroupSubset. Existing users will see the wrong selection here, and their first checkbox change will seed/save from defaults instead of their migrated subset. Please read the global value here until a per-action subset has been saved, or migrate it into action settings on first load.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 45e4ddbd-043a-424c-bfc6-ad31a7fe4a61

📥 Commits

Reviewing files that changed from the base of the PR and between 3aed5ab and 4f097c0.

📒 Files selected for processing (3)
  • packages/actions/src/actions/camera-controls.test.ts
  • packages/actions/src/actions/camera-controls.ts
  • packages/stream-deck-plugin/src/pi/camera-focus.ejs

When no per-action camera group subset exists, read the legacy global
setting to display the correct checkboxes. Prevents display mismatch
and data loss on first edit for upgrading users.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
packages/stream-deck-plugin/src/pi/camera-focus.ejs (2)

124-128: 🛠️ Refactor suggestion | 🟠 Major

Reorder the shared PI partials.

common-settings is still included before color-overrides. This template family expects color-overrides between the action-specific controls and the global section, with common-settings immediately after it.

♻️ Suggested fix
-		<%- include('common-settings') %>
-
-		<%- include('color-overrides', { slots: ['backgroundColor', 'textColor', 'graphic1Color'], defaults: require('./data/color-defaults.json')['camera-focus'] }) %>
+		<%- include('color-overrides', { slots: ['backgroundColor', 'textColor', 'graphic1Color'], defaults: require('./data/color-defaults.json')['camera-focus'] }) %>
+
+		<%- include('common-settings') %>
As per coding guidelines: Property Inspector templates must include color-overrides partial with slots and defaults between action-specific settings and global sections; Property Inspector templates must include common-settings partial after color-overrides for flags overlay and future common settings.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stream-deck-plugin/src/pi/camera-focus.ejs` around lines 124 - 128,
Reorder the PI partial includes so that the color-overrides partial is placed
between the action-specific controls and the global section, and the
common-settings partial is included immediately after color-overrides;
specifically, move the include for "common-settings" to follow the include for
"color-overrides" (which uses slots
['backgroundColor','textColor','graphic1Color'] and defaults from
'./data/color-defaults.json'['camera-focus']), keeping the existing
"global-settings" include after them.

271-307: ⚠️ Potential issue | 🟠 Major

Make the legacy fallback derive from both subscriptions.

Lines 275-306 only consult legacyGroups from inside the per-action callback. If useSettings() resolves before useGlobalSettings(), an upgraded instance renders defaults and the first save can overwrite the legacy subset instead of preserving it. Assigning parsed.groups directly also makes empty/sparse stored subsets render as default or partial checkboxes, even though packages/actions/src/actions/camera-controls.ts treats them as explicit subsets.

💡 Suggested fix
-					// Set up per-action settings for camera groups, with legacy global fallback
-					var legacyGroups = null;
+					// Set up per-action settings for camera groups, with legacy global fallback
+					var actionGroups;
+					var legacyGroups;
+
+					function parseStoredGroups(value) {
+						if (!value) return undefined;
+						try {
+							var parsed = JSON.parse(value);
+							if (!parsed || !parsed.groups || typeof parsed.groups !== "object") {
+								return undefined;
+							}
+
+							var normalized = {};
+							for (var i = 0; i < ALL_GROUPS.length; i++) {
+								var name = ALL_GROUPS[i];
+								normalized[name] = parsed.groups[name] === true;
+							}
+							return normalized;
+						} catch (e) {
+							return undefined;
+						}
+					}
+
+					function syncCurrentGroups() {
+						if (actionGroups !== undefined) {
+							currentGroups = Object.assign({}, actionGroups);
+						} else if (legacyGroups !== undefined) {
+							currentGroups = Object.assign({}, legacyGroups);
+						} else {
+							currentGroups = {};
+						}
+						renderCheckboxes();
+					}
 					if (window.SDPIComponents) {
 						// Read legacy global setting (for users upgrading from shared selection)
 						window.SDPIComponents.useGlobalSettings(SETTING_KEY, function (value) {
-							if (value) {
-								try {
-									var parsed = JSON.parse(value);
-									if (parsed && parsed.groups) {
-										legacyGroups = parsed.groups;
-									}
-								} catch (e) {
-									// ignore
-								}
-							}
+							legacyGroups = parseStoredGroups(value);
+							syncCurrentGroups();
 						});
 
 						var subsetHook = window.SDPIComponents.useSettings(SETTING_KEY, function (value) {
-							if (value) {
-								try {
-									var parsed = JSON.parse(value);
-									if (parsed && parsed.groups) {
-										currentGroups = parsed.groups;
-									} else {
-										currentGroups = {};
-									}
-								} catch (e) {
-									currentGroups = {};
-								}
-							} else if (legacyGroups) {
-								// No per-action value yet — seed from legacy global setting
-								currentGroups = legacyGroups;
-							} else {
-								currentGroups = {};
-							}
-							renderCheckboxes();
+							actionGroups = parseStoredGroups(value);
+							syncCurrentGroups();
 						});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stream-deck-plugin/src/pi/camera-focus.ejs` around lines 271 - 307,
The per-action settings subscription (window.SDPIComponents.useSettings with
SETTING_KEY) currently only falls back to legacyGroups when its callback runs,
which can lose the legacy subset if useSettings fires before useGlobalSettings;
also assigning parsed.groups directly treats empty/sparse subsets as undefined.
Fix by making both subscriptions populate a shared canonical value: in the
useGlobalSettings callback set legacyGroups to a defensive clone of
parsed.groups (or {} if parsed has an explicit empty subset), and in the
useSettings callback merge/choose between the explicit per-action parsed.groups
(including empty maps as valid) and legacyGroups if per-action is absent; update
currentGroups accordingly and then call renderCheckboxes(); reference the
legacyGroups variable, window.SDPIComponents.useGlobalSettings,
window.SDPIComponents.useSettings, currentGroups, and renderCheckboxes to locate
the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/stream-deck-plugin/src/pi/camera-focus.ejs`:
- Around line 124-128: Reorder the PI partial includes so that the
color-overrides partial is placed between the action-specific controls and the
global section, and the common-settings partial is included immediately after
color-overrides; specifically, move the include for "common-settings" to follow
the include for "color-overrides" (which uses slots
['backgroundColor','textColor','graphic1Color'] and defaults from
'./data/color-defaults.json'['camera-focus']), keeping the existing
"global-settings" include after them.
- Around line 271-307: The per-action settings subscription
(window.SDPIComponents.useSettings with SETTING_KEY) currently only falls back
to legacyGroups when its callback runs, which can lose the legacy subset if
useSettings fires before useGlobalSettings; also assigning parsed.groups
directly treats empty/sparse subsets as undefined. Fix by making both
subscriptions populate a shared canonical value: in the useGlobalSettings
callback set legacyGroups to a defensive clone of parsed.groups (or {} if parsed
has an explicit empty subset), and in the useSettings callback merge/choose
between the explicit per-action parsed.groups (including empty maps as valid)
and legacyGroups if per-action is absent; update currentGroups accordingly and
then call renderCheckboxes(); reference the legacyGroups variable,
window.SDPIComponents.useGlobalSettings, window.SDPIComponents.useSettings,
currentGroups, and renderCheckboxes to locate the code to change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9630741a-7795-445e-b3d9-4b5e7a81571f

📥 Commits

Reviewing files that changed from the base of the PR and between 4f097c0 and aaf5250.

📒 Files selected for processing (1)
  • packages/stream-deck-plugin/src/pi/camera-focus.ejs

@niklam niklam merged commit 58ac795 into master Mar 28, 2026
5 checks passed
@niklam niklam deleted the fix/208-camera-group-per-instance branch March 28, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Camera Controls: camera group selection is shared across all instances

1 participant