From 550ab42f9a39315e7a33237510f49a88129f6209 Mon Sep 17 00:00:00 2001 From: Chuck Date: Sun, 11 Jan 2026 13:40:33 -0500 Subject: [PATCH] fix: Use JSON encoding for bracket-notation arrays and add sentinel for clearing Fix bracket-notation array handling to prevent data loss: 1. Use JSON encoding instead of comma-join (lines 3358-3359): - Comma-join breaks if option values contain commas - Switch to json.dumps() to encode array values as JSON strings - _parse_form_value_with_schema() already handles JSON arrays correctly - Preserves values with commas, special characters, etc. 2. Add sentinel hidden input for clearing arrays: - Add hidden input with name="field[]" value="" in checkbox-group template - Ensures field is always submitted, even when all checkboxes unchecked - Backend filters out sentinel empty strings to detect empty array - Allows users to clear array to [] by unchecking all options 3. Update backend to handle sentinel: - Filter out sentinel empty strings from bracket notation values - Empty array (all unchecked) is represented as "[]" JSON string - Properly handles both sentinel-only (empty array) and sentinel+values cases This fixes data loss when: - Option values contain commas (comma-join corruption) - All checkboxes are unchecked (field omitted from form, can't clear to []) --- web_interface/blueprints/api_v3.py | 25 +++++++++++++------ web_interface/static/v3/plugins_manager.js | 3 +++ .../templates/v3/partials/plugin_config.html | 3 +++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/web_interface/blueprints/api_v3.py b/web_interface/blueprints/api_v3.py index ffec483c..ee98320a 100644 --- a/web_interface/blueprints/api_v3.py +++ b/web_interface/blueprints/api_v3.py @@ -3337,25 +3337,34 @@ def save_plugin_config(): # First pass: handle bracket notation array fields (e.g., "field_name[]" from checkbox-group) # These fields use getlist() to preserve all values, then replace in form_data + # Sentinel empty value ("") allows clearing array to [] when all checkboxes unchecked bracket_array_fields = {} # Maps base field path to list of values for key in request.form.keys(): # Check if key ends with "[]" (bracket notation for array fields) if key.endswith('[]'): base_path = key[:-2] # Remove "[]" suffix values = request.form.getlist(key) - if values: - bracket_array_fields[base_path] = values - # Remove the bracket notation key from form_data if present - if key in form_data: - del form_data[key] + # Filter out sentinel empty string - if only sentinel present, array should be [] + # If sentinel + values present, use the actual values + filtered_values = [v for v in values if v and v.strip()] + # If no non-empty values but key exists, it means all checkboxes unchecked (empty array) + bracket_array_fields[base_path] = filtered_values + # Remove the bracket notation key from form_data if present + if key in form_data: + del form_data[key] - # Process bracket notation fields and add to form_data as comma-separated strings + # Process bracket notation fields and add to form_data as JSON strings + # Use JSON encoding instead of comma-join to handle values containing commas + import json for base_path, values in bracket_array_fields.items(): # Get schema property to verify it's an array base_prop = _get_schema_property(schema, base_path) if base_prop and base_prop.get('type') == 'array': - # Combine values into comma-separated string for consistent parsing - combined_value = ', '.join(str(v) for v in values if v) + # Filter out empty values and sentinel empty strings + filtered_values = [v for v in values if v and v.strip()] + # Encode as JSON array string (handles values with commas correctly) + # Empty array (all unchecked) is represented as "[]" + combined_value = json.dumps(filtered_values) form_data[base_path] = combined_value logger.debug(f"Processed bracket notation array field {base_path}: {values} -> {combined_value}") diff --git a/web_interface/static/v3/plugins_manager.js b/web_interface/static/v3/plugins_manager.js index c75b75ad..2206fafa 100644 --- a/web_interface/static/v3/plugins_manager.js +++ b/web_interface/static/v3/plugins_manager.js @@ -3034,6 +3034,9 @@ function generateFieldHtml(key, prop, value, prefix = '') { html += ``; // Hidden input to store selected values as JSON array (like array-of-objects pattern) html += ``; + // Sentinel hidden input with bracket notation to allow clearing array to [] when all unchecked + // This ensures the field is always submitted, even when all checkboxes are unchecked + html += ``; } else if (xWidgetValue === 'custom-feeds' || xWidgetValue2 === 'custom-feeds') { // Custom feeds widget - check schema validation first const itemsSchema = prop.items || {}; diff --git a/web_interface/templates/v3/partials/plugin_config.html b/web_interface/templates/v3/partials/plugin_config.html index 05c67299..6b288c76 100644 --- a/web_interface/templates/v3/partials/plugin_config.html +++ b/web_interface/templates/v3/partials/plugin_config.html @@ -180,6 +180,9 @@ {# Hidden input to store selected values as JSON array (like array-of-objects pattern) #} + {# Sentinel hidden input with bracket notation to allow clearing array to [] when all unchecked #} + {# This ensures the field is always submitted, even when all checkboxes are unchecked #} + {% else %} {# Check for custom-feeds widget first #} {% set items_schema = prop.get('items') or {} %}