v0.13.0-alpha — enhancements: SEO, analytics, resolver cache
Pre-releaseThe first round of enhancements on top of the feature-complete plugin. Three additions, each verified end-to-end on the live 100k-product stack.
What's new
SEO for filtered pages
Faceted URLs bloat crawl budget and spawn near-duplicate pages, and general SEO plugins don't understand the ?hof[*] query shape — so HOF owns the faceted signals via a new SeoManager:
rel=canonicalto the filter-stripped base URL (deferred when Yoast / Rank Math / AIOSEO / SEOPress is active, to avoid a double tag).noindex,followonce a configurable number of facets are stacked (default 2 — single-facet landing pages stay indexable while the combinatorial long tail doesn't), emitted via the corewp_robotsfilter so it composes with other plugins.- An active-filters title suffix so an indexed single-facet page reads meaningfully in search results.
Settings persist to hof_seo, exposed via a new admin SEO screen and GET|POST /seo-settings. Decision logic is pure and unit-tested (12 cases).
Analytics dashboard
The telemetry recorder now captures per-facet/value usage and zero-result filter combinations — one signal per /filter action, buffered in memory and flushed at shutdown (with caps), so it never adds request latency. snapshot() gains p50/p95/p99 resolver latency.
The admin Dashboard renders most-used facets (usage bars), "filters that find nothing," the latency percentiles, and a dead-facets callout (configured but never applied by a shopper). Covered by 6 tests.
Resolver result-set cache
resolve() and resolve_ids() now cache their output in the object cache, keyed by (index version, filter state). The Indexer bumps hof_index_version on every write, so invalidation is O(1) — a bump orphans every stale key.
Benchmarking on the live 100k stack drove the scope: resolve_ids() was already ~8ms p95, but resolve() — IDs + N drill-down counts behind /filter — was ~465ms, the real bottleneck. Cached repeat hits are sub-millisecond. Most effective with a persistent object cache (Redis / Memcached); kill switch hof_resolver_cache_enabled. Covered by 4 tests.
Deliberately not done: LIMIT/OFFSET pushdown into resolve_ids() (unsafe — QueryHook needs the full ID set for post__in) and mysqli_poll parallel legs (deferred per the Phase-1 note).
Verification
- PHP: 120 tests / 265 assertions;
php -lclean - JS: 35 tests; production Vite build clean
- Markdown: lint clean
- Live stack: cache 465ms → sub-ms on repeat; version invalidation confirmed on
reindex_object; SEO canonical/robots/title verified
Upgrade
git pull && composer install && npm installNo schema change (hof_db_version 1.2.0). No reindex needed. All three features are additive — SEO defaults are conservative, the cache is transparent and version-invalidated, analytics capture is passive.
Full changelog: CHANGELOG.md