Keep our fixes over the merged auto-repair PR:
- plugin_manager: backup dir skip + clear-before-update
- state_reconciliation: no skip on missing 'enabled', default True
- store_manager: resolved_id for monorepo migration
- app.py: reconciliation threading lock + return type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add AbortController with 10s timeout so a hanging fetch doesn't leave
data-loaded set and block retries. Timer is cleared in both success
and error paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- plugin_manager.py: skip directories containing '.standalone-backup-'
during discovery scan, matching state_reconciliation.py behavior and
preventing backup manifests from overwriting live plugin entries
- base.html: fix hx-on::htmx:response-error → hx-on::response-error
(the :: shorthand already adds the htmx: prefix, so the original
syntax resolved to htmx:htmx:response-error making the handler dead)
Skipped findings:
- web-ui-info in _SYSTEM_CONFIG_KEYS: it's a real plugin with manifest.json
and config entry, not a system key
- store_manager config key migration: valid feature request for handling
ledmatrix- prefix rename, but new functionality outside this PR scope
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- plugin_manager.py: clear plugin_manifests/plugin_directories before update
to prevent ghost entries for uninstalled plugins persisting across scans
- state_reconciliation.py: remove 'enabled' key check that skipped legacy
plugin configs, default to enabled=True matching PluginManager.load_plugin
- app.py: add threading.Lock around reconciliation start guard to prevent
race condition spawning duplicate threads; add -> None return annotation
- store_manager.py: use resolved registry ID (alt_id) instead of original
plugin_id when reinstalling during monorepo migration
- base.html: check Response.ok in loadPluginsDirect fallback; trigger
fallback on tab click when HTMX unavailable; remove active-tab check
from 5-second timeout so content preloads regardless
Skipped: api_v3.py secret redaction suggestion — the caller at line 2539
already tries schema-based mask_secret_fields() before falling back to
_conservative_mask_config, making the suggested change redundant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Plugin Manager tab was the only tab using a custom window.loadPluginsTab()
function with plain fetch() instead of HTMX. This caused a race condition where
plugins_manager.js listened for htmx:afterSwap to initialize, but that event
never fired for the custom fetch. Users had to navigate to a plugin config tab
and back to trigger initialization.
Changes:
- Switch plugins tab to hx-get/hx-trigger="revealed" matching all other tabs
- Remove ~560 lines of dead code (script extraction for a partial with no scripts,
nested retry intervals, inline HTML card rendering fallbacks)
- Add simple loadPluginsDirect() fallback for when HTMX fails to load
- Remove typeof htmx guard on afterSwap listener so it registers unconditionally
- Tighten afterSwap target check to avoid spurious re-init from other tab swaps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PluginManager thread safety:
- Add RLock protecting plugin_manifests and plugin_directories
- Build scan results locally in _scan_directory_for_plugins, then update
shared state under lock
- Protect reads in get_plugin_info, get_all_plugin_info,
get_plugin_directory, get_plugin_display_modes, find_plugin_for_mode
- Protect manifest mutation in reload_plugin
- Prevents races between background reconciliation thread and request
handlers reading plugin state
Reconciliation retry:
- Clear _reconciliation_started on exception so next request retries
- Check result.reconciliation_successful before marking done
- Reset _reconciliation_started on non-success results to allow retry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move backup-folder filter from _get_config_state to _get_disk_state
where the artifact actually lives
- Run startup reconciliation in a background thread so requests aren't
blocked by plugin reinstallation
- Set _reconciliation_done only after success so failures allow retries
- Replace print() with proper logger in reconciliation
- Wrap load_schema in try/except so exceptions fall through to
conservative masking instead of 500
- Handle list values in _conservative_mask_config for nested secrets
- Remove duplicate import re
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The store registry uses unprefixed IDs (e.g., 'weather') while older
installs used prefixed config keys (e.g., 'ledmatrix-weather'). Both
update_plugin() and auto-repair now try the unprefixed ID as a fallback
when the prefixed one isn't found in the registry.
Also filters system config keys (schedule, display, etc.) from
reconciliation to avoid false positives.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Plugins whose directories are missing (failed update, migration, etc.)
now get automatically reinstalled from the store on startup. The config
endpoint no longer returns a hard 500 when a schema is unavailable —
it falls back to conservative key-name-based masking so the settings
page stays functional.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>