Fix: Dashboard video analytics widget was reading SAPI responses at the wrong path ([‘count’] / [‘facets’] instead of [‘body’][‘total’] / [‘body’][‘facets’], and facet entries use ‘value’, not ‘val’). $total_views and $total_inits always stayed 0, so the widget always rendered its “No video views recorded” empty state — even on publications with plenty of traffic. After upgrade the widget shows real data.
UX: New admin_notices banner warns administrators when the plugin is installed but has no API credentials yet, with a one-click link to the settings page. Gated on manage_options, suppressed on the settings page itself, auto-clears as soon as credentials are saved.
UX: When a SAPI analytics call fails the widget now renders a red error panel naming the failed sections and writes a structured error_log entry, instead of silently falling back to the empty-state copy. Makes “API outage” visually distinct from “publication has no traffic”.
Observability: On cache skip (at least one of the four analytics calls errored) the plugin now writes a single summary error_log line listing which section(s) failed, so transient SAPI outages surface in debug.log without waiting for a dashboard render.
Performance: Dashboard widget transient TTL reduced from 15 to 5 minutes for slightly fresher stats; cache still busts immediately on credential changes via SapiClient::clearCache(), and errored responses are still never cached.
Tests: +34 assertions in AnalyzeWidgetDataTest locking the SAPI envelope contract against the exact response-shape drift that caused the 2.0.1 regression.
2.0.1
Security: ContentGate::injectJwt now fails closed at the depth cap (previously returned un-gated content, so a crafted nested-base64 payload could bypass the gate).
Security: Library AJAX endpoint now validates bb_folder and bb-sourcetype against the same regex the REST /search_media_clips route uses (closes a Solr-fq injection surface the REST path had already closed).
Hardening: bulk_action now verifies the CSRF nonce before the capability check (defence in depth).
Observability: Content gate logs each distinct failure reason (SAPI disconnected / SAPI error / 404 / missing shared secret) and records a rolling one-hour tally. A new admin-notices banner surfaces the tally so operators can tell “outage” from “misconfig” instead of all paths rendering the same viewer message.
Observability: JWT pepper persistence failure now triggers a persistent notice-error admin banner (manage_options-gated); previously every gated video silently failed verification with no signal.
UX: Library AJAX endpoints (load_library_videos, load_library_playlists) surface the SAPI Error message via wp_send_json_error 502 instead of the prior generic 200 + “No data”. Auth failures are now distinguishable from legitimately-empty libraries.
Compat: Replaced array_filter(…, ‘strlen’) in CtaTranslator with a closure (PHP 8.1 deprecation, removed in 9).
Docs: Corrected inaccurate comments in CtaTranslator (widget types) and subtitle upload (single-POST behaviour).
Tests: +10 assertions via ContentGateFailsClosedTest, UninstallAllowlistTest, SapiClientInvalidateClipCppCacheTest; updated ContentGateInjectJwtTest::test_depth_cap_fails_closed to lock the fail-closed invariant.
2.0.0
New: Direct browser-to-storage upload via Uppy (no PHP upload limits)
New: Gutenberg blocks with server-side rendering and JSON-LD SEO
New: Channel embed Gutenberg block with channel selector
New: oEmbed — paste BB video URLs directly to embed
New: Video XML sitemap integration
New: AI transcript display below videos (SEO-indexed)
New: Video chapters with clickable timestamps (opt-in)
New: Interactive video overlay support
New: Folder tree browsing in library and media picker
New: Video analytics dashboard widget
New: Inline metadata editing in Media Library
New: JWT-based content protection via WordPress roles
New: Test Connection button on settings page
Security: All AJAX handlers verify capabilities + nonces