-
-
Notifications
You must be signed in to change notification settings - Fork 18
Fix/duplicate display settings #173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…in install Remove compatible_versions from required fields in install_from_url method to match install_plugin behavior. This allows installing plugins from URLs without manifest version requirements, consistent with store plugin installation.
- Add onchange handlers to key and value inputs for existing patternProperties fields - Fixes bug where editing existing custom RSS feeds didn't save changes - Ensures hidden JSON input field is updated when users edit feed entries - Affects all plugins using patternProperties (custom_feeds, feed_logo_map, etc.)
- Add support for rendering arrays of objects in web UI (for custom_feeds) - Implement add/remove/update functions for array-of-objects widgets - Support file-upload widgets within array items - Update form data handling to support array JSON data fields
Update version parameter to force browser to load new JavaScript with array-of-objects widget support.
Move the array-of-objects widget detection to the top of the array handler so it's checked before file-upload and checkbox-group widgets. This ensures custom_feeds is properly detected as an array of objects.
Add detection and rendering for array-of-objects in the Jinja2 template (plugin_config.html). This enables the custom_feeds widget to display properly with name, URL, enabled checkbox, and logo upload fields. The widget is detected by checking if prop.items.type == 'object' && prop.items.properties, and is rendered before the file-upload widget check.
Explicitly use window.addArrayObjectItem, window.removeArrayObjectItem, etc. in the template to ensure the functions are accessible from inline event handlers. Also add safety checks to prevent errors if functions aren't loaded yet.
Prevent display settings from being saved at both nested (display.hardware/runtime) and root level. The save_main_config function was processing display fields twice - once correctly in the nested structure, and again in the catch-all section creating root-level duplicates. Added display_fields to the skip list in the catch-all section to prevent root-level duplicates. All code expects the nested format, so this ensures consistency.
📝 WalkthroughWalkthroughThis PR enhances the plugin system with relaxed manifest validation, improved configuration management, and expanded UI support for complex array-of-objects structures in plugin forms. Changes span manifest requirements, configuration merge logic, and frontend form rendering for nested array items. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Fix all issues with AI Agents 🤖
In @src/plugin_system/store_manager.py:
- Line 977: The change removed 'version' and 'display_modes' from
required_fields in the manifest validation, but other code (e.g.,
state_reconciliation.py's manifest usage and multiple tests that assert
manifest['display_modes'][0]) still expects them; restore 'version' and
'display_modes' to required_fields in the declaration (the variable named
required_fields) so new plugin manifests always include them, or if you prefer
to allow omission, update all call sites to use safe accessors (e.g.,
manifest.get('version') with a sensible default and
manifest.get('display_modes', []) before indexing) and adjust tests to assert
the safe defaults instead — pick one approach and apply it consistently across
the manifest validation, the state reconciliation logic (the code that reads
manifest.get('version')/manifest['display_modes']), and the tests.
In @web_interface/static/v3/plugins_manager.js:
- Around line 2468-2573: renderArrayObjectItem currently renders non-editable
properties (like the logo object) but updateArrayObjectData rebuilds items only
from visible inputs, causing those properties to be lost; fix by changing
updateArrayObjectData to initialize each rebuilt item from the existing JSON
value for that item (merge/overlay input-derived fields onto the existing item
object) instead of creating a new object from inputs only, and ensure file
handlers handleArrayObjectFileUpload and removeArrayObjectFile update the
underlying item's data (e.g., set/remove data-file-data or directly mutate the
stored item) so file metadata persists; locate and modify the
updateArrayObjectData function and the TODOs in
handleArrayObjectFileUpload/removeArrayObjectFile to perform the merge and
persist file metadata rather than relying solely on visible inputs.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/plugin_system/store_manager.py (1)
985-993: Fix manifest validation to match schema requirements.The manifest schema requires
compatible_versions(it's in the schema's "required" array withminItems: 1), but the code doesn't enforce this during installation. Lines 978–983 only validate['id', 'name', 'class_name']as required, whilecompatible_versionsvalidation on lines 206–210 is conditional—it only checks if the field exists, not that it must exist. This creates a schema/implementation mismatch where plugins can install without a required field, violating the manifest specification.Either add
compatible_versionsto the required fields check (lines 978–983) or update the schema to make it optional if intentionally allowing plugins without it.web_interface/static/v3/plugins_manager.js (1)
2876-3068: Block‑scope bug and array‑type handling can throw at runtimeInside the
prop.type === 'array'branch:
xWidgetValue/xWidgetValue2are declared withconstinside theelseblock (Line ~2907 onward), but are then used in the outer scope (if (xWidgetValue === 'file-upload' || xWidgetValue2 === 'file-upload')etc.). This will cause aReferenceError: xWidgetValue is not definedas soon as any array field is processed, breaking form generation for all arrays (not just arrays‑of‑objects).As currently written, the “array‑of‑objects” path and the file‑upload / checkbox‑group / plain‑array paths are interleaved, making the control flow fragile.
Refactor to keep all non–array‑of‑objects handling (including
x-widgetdetection) inside theelseblock so theconstbindings are in scope, e.g.:Suggested restructuring of the array branch
} else if (prop.type === 'array') { // Check array-of-objects FIRST if (prop.items && prop.items.type === 'object' && prop.items.properties) { // existing array-of-objects rendering ... - } else { - // Check if this is a file upload widget - try multiple ways to access x-widget - const hasXWidget = prop.hasOwnProperty('x-widget'); - const xWidgetValue = prop['x-widget']; - const xWidgetValue2 = prop['x-widget'] || prop['x-widget'] || prop.xWidget; - - console.log(`[DEBUG] Array field ${fullKey}:`, { ... }); - } - - // Check for file-upload widget - be more defensive - if (xWidgetValue === 'file-upload' || xWidgetValue2 === 'file-upload') { - ... - } else if (xWidgetValue === 'checkbox-group' || xWidgetValue2 === 'checkbox-group') { - ... - } else { - // Regular array input - ... - } + } else { + const hasXWidget = prop.hasOwnProperty('x-widget'); + const xWidgetValue = prop['x-widget']; + const xWidgetValue2 = prop['x-widget'] || prop['x_widget'] || prop.xWidget; + + console.log(`[DEBUG] Array field ${fullKey}:`, { /* existing debug fields */ }); + + // File-upload widget + if (xWidgetValue === 'file-upload' || xWidgetValue2 === 'file-upload') { + // existing file-upload rendering... + } else if (xWidgetValue === 'checkbox-group' || xWidgetValue2 === 'checkbox-group') { + // existing checkbox-group rendering... + } else { + // Regular array input (existing code) + } + } }Also note the small typo in
xWidgetValue2(prop['x-widget'] || prop['x-widget'] || prop.xWidget)—the second variant should likely beprop['x_widget']. Low‑severity but easy to fix while you’re in this block.
🧹 Nitpick comments (3)
web_interface/templates/v3/partials/plugin_config.html (1)
61-168: Array-of-objects UI wiring looks solid; consider minor JS call consistencyThe new
arraybranch correctly detectsitems.type == 'object', renders per-item editors, and keeps a JSON copy in the hidden{{ full_key }}_datafield for the JS submit handler to serialize back into the real config field. The use oftojson|safeindata-file-dataand the guardedwindow.handleArrayObjectFileUpload/window.removeArrayObjectFile/window.addArrayObjectItemcalls is sound.One small polish: in the text-input branch you call
onchange="updateArrayObjectData('{{ field_id }}')"without thewindow.prefix or a function-existence check, while the boolean branch usesif (typeof window.updateArrayObjectData === 'function') { … }. For robustness when the JS isn’t loaded yet (or during partial reloads), you might align this with the guardedwindow.updateArrayObjectDatapattern, but behavior is otherwise correct.web_interface/static/v3/plugins_manager.js (2)
2225-2234: Hidden_dataJSON handling is good, but other paths still ignore itParsing
_datafields into objects/arrays and short‑circuiting normal processing looks correct and unblocks patternProperties / array‑of‑objects for the main submit handler. However,syncFormToJsonand the older document‑levelsubmitlistener forplugin-config-form(around Line 5460+) still treat all fields as flat scalars and do not understand the new*_datacontract, so switching to JSON view or relying on that fallback can lose or mis‑shape these complex values.Consider either:
- Removing the legacy document listener and routing all submits through
handlePluginConfigSubmit, and- Mirroring the
_dataJSON parsing insyncFormToJsonif JSON view is expected to round‑trip these structures.
2617-2625: Key–value patternProperties syncing looks correct; minor cleanup possibleThe added
data-key-index/data-value-indexattributes andonchange="updateKeyValuePairData(...)"wiring keep the hidden JSON_datafield in sync and should avoid the old “stale pairs” problems. The reindexing logic inremoveKeyValuePairalso correctly updates names, indices, and handlers.
updateKeyValuePairDatano longer uses itsfullKeyparameter (onlyfieldIdand the DOM), so you can safely drop that argument from the signature and all callers in a later cleanup; it’s not urgent.Also applies to: 3321-3439
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
plugins/7-segment-clocksrc/plugin_system/store_manager.pyweb_interface/blueprints/api_v3.pyweb_interface/static/v3/plugins_manager.jsweb_interface/templates/v3/base.htmlweb_interface/templates/v3/partials/plugin_config.html
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py
📄 CodeRabbit inference engine (.cursor/rules/coding-standards.mdc)
**/*.py: Prefer clear, readable code over clever optimizations (Simplicity First principle)
Make intentions clear through naming and structure (Explicit over Implicit principle)
Validate inputs and handle errors early (Fail Fast principle)
Use docstrings for classes and complex functions
Use PascalCase for class names (e.g.,NHLRecentManager)
Use snake_case for function and variable names (e.g.,fetch_game_data)
Use UPPER_SNAKE_CASE for constants (e.g.,ESPN_NHL_SCOREBOARD_URL)
Use leading underscore for private methods (e.g.,_fetch_data)
Use structured logging with context (e.g.,[NHL Recent]) for logging messages
Catch specific exceptions, not bareexcept:statements
Provide user-friendly error messages that explain what went wrong and potential solutions
Implement graceful degradation to continue operation when non-critical features fail
Use type hints for function parameters and return values
Validate required configuration fields on initialization
Provide sensible default values in code rather than in configuration files
Handle different deployment contexts with environment awareness in code
**/*.py: Code must run on Raspberry Pi, not Windows development machine
Optimize code for Raspberry Pi's limited RAM and CPU capabilities
Clean up resources regularly to manage memory effectively
Implement comprehensive logging for remote debugging on Raspberry Pi
Provide clear error messages for troubleshooting
Files:
web_interface/blueprints/api_v3.pysrc/plugin_system/store_manager.py
**/*manager*.py
📄 CodeRabbit inference engine (.cursor/rules/coding-standards.mdc)
All sports managers should implement the BaseManager pattern with
__init__(self, config, display_manager, cache_manager),update()for fetching/processing data, anddisplay(self, force_clear=False)for rendering
Files:
src/plugin_system/store_manager.py
src/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
All sports, weather, and stock managers should be organized in src/ directory
src/**/*.py: Use rpi-rgb-led-matrix library for hardware control on Raspberry Pi
Use cache_manager.py for data persistence and caching
Implement non-blocking data fetching using background services
Minimize unnecessary redraws to optimize LED matrix display performance
Implement health checks and status reporting
Provide web interface for remote configuration and monitoring on Raspberry Pi
Files:
src/plugin_system/store_manager.py
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: ChuckBuilds/LEDMatrix PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-27T19:16:24.801Z
Learning: Applies to plugins/*/manifest.json : Validate manifest.json syntax as valid JSON and ensure class_name matches actual class name
Learnt from: CR
Repo: ChuckBuilds/LEDMatrix PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-27T19:16:24.801Z
Learning: Applies to plugins/*/manifest.json : `manifest.json` must include required fields: id, name, version, entry_point, class_name, and display_modes
Learnt from: CR
Repo: ChuckBuilds/LEDMatrix PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-27T19:16:24.801Z
Learning: Applies to plugins/*/config_schema.json : `config_schema.json` must define JSON schema for plugin configuration validation
📚 Learning: 2025-12-27T19:16:24.801Z
Learnt from: CR
Repo: ChuckBuilds/LEDMatrix PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-27T19:16:24.801Z
Learning: Applies to plugins/*/manifest.json : `manifest.json` must include required fields: id, name, version, entry_point, class_name, and display_modes
Applied to files:
src/plugin_system/store_manager.py
📚 Learning: 2025-12-27T19:16:24.801Z
Learnt from: CR
Repo: ChuckBuilds/LEDMatrix PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-27T19:16:24.801Z
Learning: Applies to plugins/*/manifest.json : Validate manifest.json syntax as valid JSON and ensure class_name matches actual class name
Applied to files:
src/plugin_system/store_manager.py
📚 Learning: 2025-12-27T19:16:24.801Z
Learnt from: CR
Repo: ChuckBuilds/LEDMatrix PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-27T19:16:24.801Z
Learning: Applies to plugins/*/manager.py : Plugin class should implement `validate_config()` method for configuration validation
Applied to files:
src/plugin_system/store_manager.py
🧬 Code graph analysis (1)
web_interface/static/v3/plugins_manager.js (2)
web_interface/blueprints/pages_v3.py (1)
index(13-46)web_interface/app.py (1)
index(622-624)
🔇 Additional comments (4)
plugins/7-segment-clock (1)
1-1: Limited visibility: Only submodule reference provided, cannot review actual changes.This file contains only a git submodule commit pointer update. The PR objectives describe enhancements to manifest validation, configuration management, and UI support for complex array-of-objects structures, but the actual source code changes in
src/plugin_system/store_manager.py,web_interface/api_v3.py, and related files are not included in this review.To provide a thorough review of the "duplicate display settings" fix and the configuration system improvements, please provide the modified files mentioned in the enriched summary for review.
web_interface/templates/v3/base.html (1)
4821-4821: Cache-busting version bump forplugins_manager.jslooks correctUpdating the query param here is a safe way to force clients to pick up the new legacy
plugins_manager.jsbundle. Just verify there are no other hardcoded references to this script (with a different?v=) in other templates, so all entry points are consistently pointing at the same versioned URL.web_interface/blueprints/api_v3.py (1)
599-612: Skip re-merging display fields into the root config looks correctThe added guard to
continuewhenkey in display_fieldsprevents hardware/runtime display fields from being written both intocurrent_config['display'][...]and again at the root, which matches the intent to keep display settings nested. One thing to double‑check: duration fields (*_duration,default_duration,transition_duration) are still written both intodisplay.display_durationsand at the root; if you eventually want those to live only underdisplay, they’d need a similar skip path (or their own list) once the display controller no longer relies on the top‑level keys.web_interface/static/v3/plugins_manager.js (1)
3441-3586: Missing context: Please provide the review comment you'd like me to rewrite, enclosed in<review_comment>tags, along with any relevant repository context or shell script results needed for verification.
|
|
||
| # Validate manifest has required fields | ||
| required_fields = ['id', 'name', 'class_name', 'compatible_versions'] | ||
| required_fields = ['id', 'name', 'class_name'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify usage of version and display_modes fields from manifest
echo "=== Checking for manifest 'version' field usage ==="
rg -nP --type=py -C2 'manifest.*version' -g '!src/plugin_system/store_manager.py' | head -40
echo -e "\n=== Checking for manifest 'display_modes' field usage ==="
rg -nP --type=py -C2 'manifest.*display_modes' -g '!src/plugin_system/store_manager.py' | head -40
echo -e "\n=== Checking for 'version' in manifest validation or access ==="
rg -nP --type=py '\bversion' src/plugin_system/ | grep -i manifest | head -20
echo -e "\n=== Checking plugin manifest files themselves ==="
find . -name "manifest.json" -type f | head -10 | xargs cat 2>/dev/null | head -80Repository: ChuckBuilds/LEDMatrix
Length of output: 4724
Remove version and display_modes from required_fields only if all dependent code is updated.
The required_fields have been reduced to ['id', 'name', 'class_name'], removing version and display_modes. However, these fields are actively used elsewhere:
src/plugin_system/state_reconciliation.py:195accessesmanifest.get('version')for plugin state tracking- Multiple test files assert
'display_modes' in manifestand access it directly atmanifest['display_modes'][0]
Plugins installed without these fields will break test assertions and leave incomplete state records. Either restore these as required fields or update all dependent code to handle their absence gracefully.
🤖 Prompt for AI Agents
In @src/plugin_system/store_manager.py around line 977, The change removed
'version' and 'display_modes' from required_fields in the manifest validation,
but other code (e.g., state_reconciliation.py's manifest usage and multiple
tests that assert manifest['display_modes'][0]) still expects them; restore
'version' and 'display_modes' to required_fields in the declaration (the
variable named required_fields) so new plugin manifests always include them, or
if you prefer to allow omission, update all call sites to use safe accessors
(e.g., manifest.get('version') with a sensible default and
manifest.get('display_modes', []) before indexing) and adjust tests to assert
the safe defaults instead — pick one approach and apply it consistently across
the manifest validation, the state reconciliation logic (the code that reads
manifest.get('version')/manifest['display_modes']), and the tests.
| // Helper function to render a single item in an array of objects | ||
| function renderArrayObjectItem(fieldId, fullKey, itemProperties, itemValue, index, itemsSchema) { | ||
| const item = itemValue || {}; | ||
| const itemId = `${fieldId}_item_${index}`; | ||
| let html = `<div class="border border-gray-300 rounded-lg p-4 bg-gray-50 array-object-item" data-index="${index}">`; | ||
|
|
||
| // Render each property of the object | ||
| const propertyOrder = itemsSchema['x-propertyOrder'] || Object.keys(itemProperties); | ||
| propertyOrder.forEach(propKey => { | ||
| if (!itemProperties[propKey]) return; | ||
|
|
||
| const propSchema = itemProperties[propKey]; | ||
| const propValue = item[propKey] !== undefined ? item[propKey] : propSchema.default; | ||
| const propLabel = propSchema.title || propKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); | ||
| const propDescription = propSchema.description || ''; | ||
| const propFullKey = `${fullKey}[${index}].${propKey}`; | ||
|
|
||
| html += `<div class="mb-3">`; | ||
|
|
||
| // Handle file-upload widget (for logo field) | ||
| if (propSchema['x-widget'] === 'file-upload') { | ||
| html += `<label class="block text-sm font-medium text-gray-700 mb-1">${escapeHtml(propLabel)}</label>`; | ||
| if (propDescription) { | ||
| html += `<p class="text-xs text-gray-500 mb-2">${escapeHtml(propDescription)}</p>`; | ||
| } | ||
| const uploadConfig = propSchema['x-upload-config'] || {}; | ||
| const pluginId = uploadConfig.plugin_id || (typeof currentPluginConfig !== 'undefined' ? currentPluginConfig?.pluginId : null) || (typeof window.currentPluginConfig !== 'undefined' ? window.currentPluginConfig?.pluginId : null) || 'ledmatrix-news'; | ||
| const logoValue = propValue || {}; | ||
|
|
||
| html += ` | ||
| <div class="file-upload-widget-inline"> | ||
| <input type="file" | ||
| id="${itemId}_logo_file" | ||
| accept="${(uploadConfig.allowed_types || ['image/png', 'image/jpeg', 'image/bmp']).join(',')}" | ||
| style="display: none;" | ||
| onchange="handleArrayObjectFileUpload(event, '${fieldId}', ${index}, '${propKey}', '${pluginId}')"> | ||
| <button type="button" | ||
| onclick="document.getElementById('${itemId}_logo_file').click()" | ||
| class="px-3 py-2 text-sm bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-md transition-colors"> | ||
| <i class="fas fa-upload mr-1"></i> Upload Logo | ||
| </button> | ||
| `; | ||
|
|
||
| if (logoValue.path) { | ||
| html += ` | ||
| <div class="mt-2 flex items-center space-x-2"> | ||
| <img src="/${logoValue.path}" alt="Logo" class="w-16 h-16 object-cover rounded border"> | ||
| <button type="button" | ||
| onclick="removeArrayObjectFile('${fieldId}', ${index}, '${propKey}')" | ||
| class="text-red-600 hover:text-red-800"> | ||
| <i class="fas fa-trash"></i> Remove | ||
| </button> | ||
| </div> | ||
| `; | ||
| } | ||
|
|
||
| html += `</div>`; | ||
| } else if (propSchema.type === 'boolean') { | ||
| // Boolean checkbox | ||
| html += ` | ||
| <label class="flex items-center"> | ||
| <input type="checkbox" | ||
| id="${itemId}_${propKey}" | ||
| name="${itemId}_${propKey}" | ||
| data-prop-key="${propKey}" | ||
| class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" | ||
| ${propValue ? 'checked' : ''} | ||
| onchange="updateArrayObjectData('${fieldId}')"> | ||
| <span class="ml-2 text-sm text-gray-700">${escapeHtml(propLabel)}</span> | ||
| </label> | ||
| `; | ||
| } else { | ||
| // Regular text/string input | ||
| html += ` | ||
| <label for="${itemId}_${propKey}" class="block text-sm font-medium text-gray-700 mb-1"> | ||
| ${escapeHtml(propLabel)} | ||
| </label> | ||
| `; | ||
| if (propDescription) { | ||
| html += `<p class="text-xs text-gray-500 mb-1">${escapeHtml(propDescription)}</p>`; | ||
| } | ||
| html += ` | ||
| <input type="${propSchema.format === 'uri' ? 'url' : 'text'}" | ||
| id="${itemId}_${propKey}" | ||
| name="${itemId}_${propKey}" | ||
| data-prop-key="${propKey}" | ||
| class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm bg-white text-black" | ||
| value="${escapeHtml(propValue || '')}" | ||
| placeholder="${propSchema.format === 'uri' ? 'https://example.com/feed' : ''}" | ||
| onchange="updateArrayObjectData('${fieldId}')"> | ||
| `; | ||
| } | ||
|
|
||
| html += `</div>`; | ||
| }); | ||
|
|
||
| html += ` | ||
| <button type="button" | ||
| onclick="removeArrayObjectItem('${fieldId}', ${index})" | ||
| class="mt-2 px-3 py-2 text-sm text-red-600 hover:text-red-800 hover:bg-red-50 rounded-md transition-colors"> | ||
| <i class="fas fa-trash mr-1"></i> Remove Feed | ||
| </button> | ||
| </div>`; | ||
|
|
||
| return html; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array‑of‑objects item rendering drops non‑editable fields (e.g. logos) on change
renderArrayObjectItem plus updateArrayObjectData rebuild each item solely from visible inputs (data-prop-key + checkboxes + [data-file-data]). For properties like the logo object, there is no corresponding input or data-file-data element; as soon as the user edits any field in an item, updateArrayObjectData overwrites that item based only on current inputs, silently dropping logo (and any other non‑editable fields) from *_data.
This will erase existing per‑item logo metadata on the next save, even though the user didn’t touch it.
A robust fix is to seed each rebuilt item from the existing JSON, then overlay input changes:
Proposed fix for updateArrayObjectData
window.updateArrayObjectData = function(fieldId) {
const itemsContainer = document.getElementById(fieldId + '_items');
const hiddenInput = document.getElementById(fieldId + '_data');
if (!itemsContainer || !hiddenInput) return;
-
- const items = [];
+ let existingItems = [];
+ try {
+ existingItems = JSON.parse(hiddenInput.value || '[]');
+ if (!Array.isArray(existingItems)) {
+ existingItems = [];
+ }
+ } catch (e) {
+ existingItems = [];
+ }
+
+ const items = [];
const itemElements = itemsContainer.querySelectorAll('.array-object-item');
itemElements.forEach((itemEl, index) => {
- const item = {};
+ const base = (Array.isArray(existingItems) && existingItems[index]) || {};
+ const item = { ...base };
@@
});
hiddenInput.value = JSON.stringify(items);
};Also, the file‑upload controls are currently placeholders (handleArrayObjectFileUpload / removeArrayObjectFile are TODOs and never set data-file-data), so the “Upload Logo” button appears functional but doesn’t actually persist anything. Either wire these up now or gate the widget behind a feature flag to avoid user confusion.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @web_interface/static/v3/plugins_manager.js around lines 2468-2573,
renderArrayObjectItem currently renders non-editable properties (like the logo
object) but updateArrayObjectData rebuilds items only from visible inputs,
causing those properties to be lost; fix by changing updateArrayObjectData to
initialize each rebuilt item from the existing JSON value for that item
(merge/overlay input-derived fields onto the existing item object) instead of
creating a new object from inputs only, and ensure file handlers
handleArrayObjectFileUpload and removeArrayObjectFile update the underlying
item's data (e.g., set/remove data-file-data or directly mutate the stored item)
so file metadata persists; locate and modify the updateArrayObjectData function
and the TODOs in handleArrayObjectFileUpload/removeArrayObjectFile to perform
the merge and persist file metadata rather than relying solely on visible
inputs.
Summary by CodeRabbit
Release Notes
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.