Follow-up to the previous commit addressing the Plugin Store browse path
specifically. Most users install plugins via the store (ZIP extraction,
no .git directory) so the git-info mtime cache from the previous commit
was a no-op for them; their pain was coming from /plugins/store/list.
Root cause. search_plugins() enriched each returned plugin with three
serial GitHub fetches: _get_github_repo_info (repo API), _get_latest_commit_info
(commits API), _fetch_manifest_from_github (raw.githubusercontent.com).
Fifteen plugins × three requests × serial HTTP = 30–45 sequential round
trips on every cold browse. On a Pi4 over WiFi that translated directly
into the "connecting to display" hang users reported. The commit and
manifest caches had a 5-minute TTL, so even a brief absence re-paid the
full cost.
Changes.
- ``search_plugins``: fan out per-plugin enrichment through a
``ThreadPoolExecutor`` (max 10 workers, stays well under unauthenticated
GitHub rate limits). Apply category/tag/query filters before enrichment
so we never waste requests on plugins that will be filtered out.
``executor.map`` preserves input order, which the UI depends on.
- ``commit_cache_timeout`` and ``manifest_cache_timeout``: 5 min → 30 min.
Keeps the cache warm across a realistic session while still picking up
upstream updates in a reasonable window.
- ``_get_github_repo_info`` and ``_get_latest_commit_info``: stale-on-error
fallback. On a network failure or a 403 we now prefer a previously-
cached value over the zero-default, matching the pattern already in
``fetch_registry``. Flaky Pi WiFi no longer causes star counts to flip
to 0 and commit info to disappear.
Tests (5 new in test_store_manager_caches.py).
- ``test_results_preserve_registry_order`` — the parallel map must still
return plugins in input order.
- ``test_filters_applied_before_enrichment`` — category/tag/query filters
run first so we don't waste HTTP calls.
- ``test_enrichment_runs_concurrently`` — peak-concurrency check plus a
wall-time bound that would fail if the code regressed to serial.
- ``test_repo_info_stale_on_network_error`` — repo info falls back to
stale cache on RequestException.
- ``test_commit_info_stale_on_network_error`` — commit info falls back to
stale cache on RequestException.
All 29 tests (16 reconciliation, 13 store_manager caches) pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three interacting bugs reported by a user (Discord/ericepe) on a fresh install:
1. The state reconciler retried failed auto-repairs on every HTTP request,
pegging CPU and flooding logs with "Plugin not found in registry: github
/ youtube". Root cause: ``_run_startup_reconciliation`` reset
``_reconciliation_started`` to False on any unresolved inconsistency, so
``@app.before_request`` re-fired the entire pass on the next request.
Fix: run reconciliation exactly once per process; cache per-plugin
unrecoverable failures inside the reconciler so even an explicit
re-trigger stays cheap; add a registry pre-check to skip the expensive
GitHub fetch when we already know the plugin is missing; expose
``force=True`` on ``/plugins/state/reconcile`` so users can retry after
fixing the underlying issue.
2. Uninstalling a plugin via the UI succeeded but the plugin reappeared.
Root cause: a race between ``store_manager.uninstall_plugin`` (removes
files) and ``cleanup_plugin_config`` (removes config entry) — if
reconciliation fired in the gap it saw "config entry with no files" and
reinstalled. Fix: reorder uninstall to clean config FIRST, drop a
short-lived "recently uninstalled" tombstone on the store manager that
the reconciler honors, and pass ``store_manager`` to the manual
``/plugins/state/reconcile`` endpoint (it was previously omitted, which
silently disabled auto-repair entirely).
3. ``GET /plugins/installed`` was very slow on a Pi4 (UI hung on
"connecting to display" for minutes, ~98% CPU). Root causes: per-request
``discover_plugins()`` + manifest re-read + four ``git`` subprocesses per
plugin (``rev-parse``, ``--abbrev-ref``, ``config``, ``log``). Fix:
mtime-gate ``discover_plugins()`` and drop the per-plugin manifest
re-read in the endpoint; cache ``_get_local_git_info`` keyed on
``.git/HEAD`` mtime so subprocesses only run when the working copy
actually moved; bump registry cache TTL from 5 to 15 minutes and fall
back to stale cache on transient network failure.
Tests: 16 reconciliation cases (including 5 new ones covering the
unrecoverable cache, force-reconcile path, transient-failure handling, and
recently-uninstalled tombstone) and 8 new store_manager cache tests
covering tombstone TTL, git-info mtime cache hit/miss, and the registry
stale-cache fallback. All 24 pass; the broader 288-test suite continues to
pass with no new failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>