Skip to content

Bug: Scale/Number parameters lose checked state on app restart #16

@caycecollins

Description

@caycecollins

Bug: Scale/Number parameters lose checked state on app restart

Summary

Parameters using ScaleRow (e.g., brightness_step_pct) become unchecked after restarting StreamController, while ComboRow parameters (e.g., color_name) persist correctly. The saved value remains in the settings JSON but the checkbox is not restored.

Steps to Reproduce

  1. Add a Perform Action button
  2. Set domain: light, action: turn_on, entity: any light entity
  3. Check brightness_step_pct and set a value (e.g., 11)
  4. Restart StreamController
  5. Open the button config -- brightness_step_pct is unchecked

Expected Behavior

All checked parameters should remain checked after restart, regardless of their widget type.

Root Cause

During startup, Page.initialize_actions() calls both load_initial_generative_ui() (deferred via GLib.idle_add) and on_ready(). When the deferred load_initial_generative_ui runs, it calls load_initial_ui() on every GenerativeUI object, including the action combo row.

The action combo's load_initial_ui triggers _handle_value_changed with trigger_callback=True, which invokes _on_change_action. This method unconditionally clears all parameters -- there is no guard checking whether the action actually changed:

@requires_initialization
def _on_change_action(self, _, __, ___) -> None:
"""Execute when the action is changed."""
self.settings.clear_parameters()
parameters_helper.load_parameters(self)
self._reload()

Both on_change_domain and on_change_entity guard against same-value triggers, but _on_change_action is missing this guard -- so it fires even when the action hasn't changed, wiping saved parameters on every startup.

@requires_initialization
def on_change_domain(self, _, domain, old_domain):
"""Execute when the domain is changed."""
domain = str(domain) if domain is not None else None
old_domain = str(old_domain) if old_domain is not None else None
if old_domain != domain:
entity = self.settings.get_entity()
if entity and self.track_entity:
self.plugin_base.backend.remove_tracked_entity(entity, self.refresh)
self.settings.reset(domain)
self.entity_combo.remove_all_items()
if domain:
self._load_entities()
self.set_enabled_disabled()

@requires_initialization
def on_change_entity(self, _, entity, old_entity):
"""Execute when the entity is changed."""
entity = str(entity) if entity is not None else None
old_entity = str(old_entity) if old_entity is not None else None
if old_entity == entity:
return
super().on_change_entity(_, entity, old_entity)
if entity:
parameters_helper.load_parameters(self)
self._reload()

Why ComboRow parameters survive

After clear_parameters wipes everything, load_initial_ui is called on the newly created parameter rows:

  • ComboRow parameters (e.g., color_name): ComboRow.load_initial_ui() calls widget.set_selected_item() without @signal_manager, so the GTK notify::selected signal fires, triggering ParameterComboRow._value_changed which re-checks the checkbox and re-saves the parameter.

  • ScaleRow parameters (e.g., brightness_step_pct): GenerativeUI.load_initial_ui() calls set_ui_value() with @signal_manager (signals disconnected), so no GTK signal fires. The checkbox stays unchecked and the parameter is not re-saved.

Suggested Fix

Add a same-value guard to _on_change_action, matching the existing pattern in the other on_change_* callbacks:

@requires_initialization
def _on_change_action(self, _, action, old_action) -> None:
    """Execute when the action is changed."""
    action = str(action) if action is not None else None
    old_action = str(old_action) if old_action is not None else None

    if old_action == action:
        return

    self.settings.clear_parameters()
    parameters_helper.load_parameters(self)
    self._reload()

Affected Parameter Types

Widget Type Example Param Persists? Why
ParameterComboRow color_name Yes Signal leak re-saves it
ParameterScaleRow brightness_step_pct, brightness_pct No Signal-managed, no re-save
ParameterEntryRow free-text params No Signal-managed, no re-save
ParameterSwitchRow boolean params No Signal-managed, no re-save

Environment

  • StreamController installed via AUR (streamcontroller-git)
  • HomeAssistantPlugin from git
  • Home Assistant with LIFX light entities

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions