fix(web): ensure unchecked checkboxes save as false in main config forms (#222)

* fix: remove plugin-specific calendar duration from config template

Plugin display durations should be added dynamically when plugins are
installed, not hardcoded in the template.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(web): ensure unchecked checkboxes save as false in main config forms

HTML checkboxes omit their key entirely when unchecked, so the backend
never received updates to set boolean values to false. This affected:

- vegas_scroll_enabled: Now uses _coerce_to_bool helper
- use_short_date_format: Now uses _coerce_to_bool helper
- Plugin system checkboxes (auto_discover, auto_load_enabled, development_mode):
  Now uses _coerce_to_bool helper
- Hardware checkboxes (disable_hardware_pulsing, inverse_colors, show_refresh_rate):
  Now uses _coerce_to_bool helper
- web_display_autostart: Now uses _coerce_to_bool helper

Added _coerce_to_bool() helper function that properly converts form string
values ("true", "on", "1", "yes") to actual Python booleans, ensuring
consistent JSON types in config and correct downstream boolean checks.

Also added value="true" to all main config checkboxes for consistent boolean
parsing (sends "true" instead of "on" when checked).

This is the same issue fixed in commit 10d70d91 for plugin configs, but
for the main configuration forms (display settings, general settings).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-01-29 21:49:29 -05:00
committed by GitHub
parent 68c4259370
commit 8912501604
3 changed files with 45 additions and 17 deletions

View File

@@ -65,6 +65,29 @@ def _save_config_atomic(config_manager, config_data, create_backup=True):
except Exception as e:
return False, str(e)
def _coerce_to_bool(value):
"""
Coerce a form value to a proper Python boolean.
HTML checkboxes send string values like "true", "on", "1" when checked.
This ensures we store actual booleans in config JSON, not strings.
Args:
value: The form value (string, bool, int, or None)
Returns:
bool: True if value represents a truthy checkbox state, False otherwise
"""
if value is None:
return False
if isinstance(value, bool):
return value
if isinstance(value, int):
return value == 1
if isinstance(value, str):
return value.lower() in ('true', 'on', '1', 'yes')
return False
def _get_display_service_status():
"""Return status information about the ledmatrix service."""
try:
@@ -612,7 +635,7 @@ def save_main_config():
if is_general_update:
# For checkbox: if not present in data during general update, it means unchecked
current_config['web_display_autostart'] = data.get('web_display_autostart', False)
current_config['web_display_autostart'] = _coerce_to_bool(data.get('web_display_autostart'))
if 'timezone' in data:
current_config['timezone'] = data['timezone']
@@ -633,10 +656,10 @@ def save_main_config():
if 'plugin_system' not in current_config:
current_config['plugin_system'] = {}
# Handle plugin system checkboxes
# Handle plugin system checkboxes - always set to handle unchecked state
# HTML checkboxes omit the key when unchecked, so missing key = unchecked = False
for checkbox in ['auto_discover', 'auto_load_enabled', 'development_mode']:
if checkbox in data:
current_config['plugin_system'][checkbox] = data.get(checkbox, False)
current_config['plugin_system'][checkbox] = _coerce_to_bool(data.get(checkbox))
# Handle plugins_directory
if 'plugins_directory' in data:
@@ -670,13 +693,12 @@ def save_main_config():
if 'gpio_slowdown' in data:
current_config['display']['runtime']['gpio_slowdown'] = int(data['gpio_slowdown'])
# Handle checkboxes
# Handle checkboxes - coerce to bool to ensure proper JSON types
for checkbox in ['disable_hardware_pulsing', 'inverse_colors', 'show_refresh_rate']:
current_config['display']['hardware'][checkbox] = data.get(checkbox, False)
current_config['display']['hardware'][checkbox] = _coerce_to_bool(data.get(checkbox))
# Handle display-level checkboxes
if 'use_short_date_format' in data:
current_config['display']['use_short_date_format'] = data.get('use_short_date_format', False)
# Handle display-level checkboxes (always set to handle unchecked state)
current_config['display']['use_short_date_format'] = _coerce_to_bool(data.get('use_short_date_format'))
# Handle dynamic duration settings
if 'max_dynamic_duration_seconds' in data:
@@ -696,14 +718,11 @@ def save_main_config():
vegas_config = current_config['display']['vegas_scroll']
# Ensure a default enabled value exists on first init
vegas_config.setdefault('enabled', True)
# Handle enabled checkbox only when explicitly provided
# (HTML checkbox sends "on" string when checked, omits key when unchecked)
if 'vegas_scroll_enabled' in data:
enabled_value = data['vegas_scroll_enabled']
vegas_config['enabled'] = enabled_value in (True, 'on', 'true', '1', 1)
# Handle enabled checkbox
# HTML checkboxes omit the key entirely when unchecked, so if the form
# was submitted (any vegas field present) but enabled key is missing,
# the checkbox was unchecked and we should set enabled=False
vegas_config['enabled'] = _coerce_to_bool(data.get('vegas_scroll_enabled'))
# Handle numeric settings with validation
numeric_fields = {

View File

@@ -182,6 +182,7 @@
<label class="flex items-center">
<input type="checkbox"
name="disable_hardware_pulsing"
value="true"
{% if main_config.display.hardware.disable_hardware_pulsing %}checked{% endif %}
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Disable Hardware Pulsing</span>
@@ -192,6 +193,7 @@
<label class="flex items-center">
<input type="checkbox"
name="inverse_colors"
value="true"
{% if main_config.display.hardware.inverse_colors %}checked{% endif %}
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Inverse Colors</span>
@@ -202,6 +204,7 @@
<label class="flex items-center">
<input type="checkbox"
name="show_refresh_rate"
value="true"
{% if main_config.display.hardware.show_refresh_rate %}checked{% endif %}
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Show Refresh Rate</span>
@@ -212,6 +215,7 @@
<label class="flex items-center">
<input type="checkbox"
name="use_short_date_format"
value="true"
{% if main_config.display.use_short_date_format %}checked{% endif %}
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Use Short Date Format</span>
@@ -251,6 +255,7 @@
<input type="checkbox"
id="vegas_scroll_enabled"
name="vegas_scroll_enabled"
value="true"
aria-label="Enable Vegas Scroll Mode"
{% if main_config.display.get('vegas_scroll', {}).get('enabled', false) %}checked{% endif %}
class="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">

View File

@@ -33,6 +33,7 @@
<label class="flex items-center">
<input type="checkbox"
name="web_display_autostart"
value="true"
{% if main_config.web_display_autostart %}checked{% endif %}
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Web Display Autostart</span>
@@ -118,6 +119,7 @@
<label class="flex items-center">
<input type="checkbox"
name="auto_discover"
value="true"
{% if main_config.get('plugin_system', {}).get('auto_discover', True) %}checked{% endif %}
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Auto Discover Plugins</span>
@@ -130,6 +132,7 @@
<label class="flex items-center">
<input type="checkbox"
name="auto_load_enabled"
value="true"
{% if main_config.get('plugin_system', {}).get('auto_load_enabled', True) %}checked{% endif %}
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Auto Load Enabled Plugins</span>
@@ -142,6 +145,7 @@
<label class="flex items-center">
<input type="checkbox"
name="development_mode"
value="true"
{% if main_config.get('plugin_system', {}).get('development_mode', False) %}checked{% endif %}
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<span class="ml-2 text-sm font-medium text-gray-900">Development Mode</span>