Skip to content

Conversation

@ChuckBuilds
Copy link
Owner

@ChuckBuilds ChuckBuilds commented Jan 5, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Support for complex nested object arrays in plugin configuration forms with per-item editing and add/remove functionality.
    • File upload handling for array-based plugin settings.
  • Improvements

    • Relaxed plugin installation requirements for better compatibility.
    • Version validation now provides warnings instead of blocking installations.
    • Enhanced configuration handling for display settings.

✏️ Tip: You can customize this high-level summary in your review settings.

Chuck added 12 commits January 3, 2026 10:47
…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.
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Submodule Update
plugins/7-segment-clock
Subproject commit reference updated; no functional changes.
Manifest Validation
src/plugin_system/store_manager.py
Removed mandatory compatible_versions field from required manifest fields during URL installation. Version validation now generates warnings instead of blocking installation.
Config Merge Logic
web_interface/blueprints/api_v3.py
Added guard to skip display-related keys during top-level config merge, preventing overwrites of nested display settings.
Frontend Asset Cache
web_interface/templates/v3/base.html
Updated cache-busting parameter for plugins_manager.js script.
Array-of-Objects Form Support
web_interface/static/v3/plugins_manager.js
Introduced comprehensive support for rendering and editing arrays of objects in plugin configs. Added public functions: addArrayObjectItem(), removeArrayObjectItem(), updateArrayObjectData(), renderArrayObjectItem(), handleArrayObjectFileUpload(), removeArrayObjectFile(). Enhanced form data processing to parse JSON-encoded hidden fields and detect array-of-objects structures.
Array-of-Objects Template
web_interface/templates/v3/partials/plugin_config.html
Extended template to render dynamic per-item editors for arrays of objects, including file-upload widgets, boolean fields, text inputs, and add/remove item controls. Maintains hidden JSON representation of array data.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Arrays of objects, now dancing in forms,
No strict version walls to weather the storms,
Display settings nested where they belong,
A rabbit hops through configs all day long,
Plugins bloom brighter with flexibility's song!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Fix/duplicate display settings' is vague and generic. It uses non-descriptive formatting with a slash and doesn't clearly convey what specific problem is being fixed or what the actual changes accomplish. Revise the title to be more specific and descriptive of the actual changes, such as 'Prevent display settings from being duplicated at top level' or 'Fix display settings handling in config merge logic'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

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

@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.

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 with minItems: 1), but the code doesn't enforce this during installation. Lines 978–983 only validate ['id', 'name', 'class_name'] as required, while compatible_versions validation 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_versions to 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 runtime

Inside the prop.type === 'array' branch:

  • xWidgetValue / xWidgetValue2 are declared with const inside the else block (Line ~2907 onward), but are then used in the outer scope (if (xWidgetValue === 'file-upload' || xWidgetValue2 === 'file-upload') etc.). This will cause a ReferenceError: xWidgetValue is not defined as 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-widget detection) inside the else block so the const bindings 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 be prop['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 consistency

The new array branch correctly detects items.type == 'object', renders per-item editors, and keeps a JSON copy in the hidden {{ full_key }}_data field for the JS submit handler to serialize back into the real config field. The use of tojson|safe in data-file-data and the guarded window.handleArrayObjectFileUpload/window.removeArrayObjectFile/window.addArrayObjectItem calls is sound.

One small polish: in the text-input branch you call onchange="updateArrayObjectData('{{ field_id }}')" without the window. prefix or a function-existence check, while the boolean branch uses if (typeof window.updateArrayObjectData === 'function') { … }. For robustness when the JS isn’t loaded yet (or during partial reloads), you might align this with the guarded window.updateArrayObjectData pattern, but behavior is otherwise correct.

web_interface/static/v3/plugins_manager.js (2)

2225-2234: Hidden _data JSON handling is good, but other paths still ignore it

Parsing _data fields into objects/arrays and short‑circuiting normal processing looks correct and unblocks patternProperties / array‑of‑objects for the main submit handler. However, syncFormToJson and the older document‑level submit listener for plugin-config-form (around Line 5460+) still treat all fields as flat scalars and do not understand the new *_data contract, 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 _data JSON parsing in syncFormToJson if JSON view is expected to round‑trip these structures.

2617-2625: Key–value patternProperties syncing looks correct; minor cleanup possible

The added data-key-index / data-value-index attributes and onchange="updateKeyValuePairData(...)" wiring keep the hidden JSON _data field in sync and should avoid the old “stale pairs” problems. The reindexing logic in removeKeyValuePair also correctly updates names, indices, and handlers.

updateKeyValuePairData no longer uses its fullKey parameter (only fieldId and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5f4839b and b13d680.

📒 Files selected for processing (6)
  • plugins/7-segment-clock
  • src/plugin_system/store_manager.py
  • web_interface/blueprints/api_v3.py
  • web_interface/static/v3/plugins_manager.js
  • web_interface/templates/v3/base.html
  • web_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 bare except: 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.py
  • src/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, and display(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 for plugins_manager.js looks correct

Updating the query param here is a safe way to force clients to pick up the new legacy plugins_manager.js bundle. 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 correct

The added guard to continue when key in display_fields prevents hardware/runtime display fields from being written both into current_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 into display.display_durations and at the root; if you eventually want those to live only under display, 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']
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -80

Repository: 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:195 accesses manifest.get('version') for plugin state tracking
  • Multiple test files assert 'display_modes' in manifest and access it directly at manifest['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.

Comment on lines +2468 to +2573
// 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;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants