
= 4.3.4 - 2026-07-19 =
* Fix: a stray `!box.classList.contains('vrs-gate-visible')` lock fired AFTER
  initGateVisibility(), locking the body even for verified visitors whose gate
  was never shown. Users with a valid cookie saw no popup, but had no scrollbars
  because the body was incorrectly locked. Removed the redundant lock —
  initGateVisibility already handles locking correctly when the gate becomes
  visible.

= 4.3.3 - 2026-07-19 =
* Fix: BFCache restore or stale state from a previous visit could leave
  the body with `overflow: hidden` on a page with a valid verified cookie.
  Added `vrsForceUnlockBodyScroll()` on DOMContentLoaded so a stuck lock
  is cleared before the gate re-evaluates the cookie.
* Fix: `data-redirect="hom"` (or any non-URL string) was treated as a
  valid redirect and would navigate to a 404 on success/close. New
  `vrsIsSafeRedirect()` helper rejects everything except http(s) and
  root-relative paths, and falls through to a clean close for the rest.
= 4.3.3 - 2026-07-19 =
* Fix: body scroll lock could leave the page permanently unscrollable after
  a failed verification on the live demo. The lock was set with a naked
  `document.body.style.overflow = 'hidden'` but the unlock paths were
  conditional on redirects and could be skipped on error. Replaced with
  `vrsLockBodyScroll()` / `vrsUnlockBodyScroll()` refcount-based helpers,
  a `pagehide` / `beforeunload` safety net, and an explicit `window.*`
  exposure so `veriself-frontend.js` shares the same counter.
* Tests: new BodyScrollLockTest with 9 PHPUnit assertions covering helper
  pairing, naked-write detection, pagehide/beforeunload safety net, and
  customizer-preview bypass. Full suite at 858/3377/0.

= 4.3.1 - 2026-07-19 =
* Fix: REST permission_callbacks now use a `rest_get_param()` helper that
  accepts WP_REST_Request, array, and stdClass. The initial implementation
  used `$request['nonce']` array access which fails on environments where
  WP_REST_Request's ArrayAccess is not registered.
* Tests: new RestRoutesTest with 13 PHPUnit tests covering route
  registration, permission_callback behavior, AJAX/REST inner-method
  extraction, data-rest-url attribute, get_rest_url() usage, and absence
  of internal implementation markers. Full suite at 849/3361/0.

= 4.3.0 - 2026-07-19 =
* New: REST API routes at /wp-json/veriself/v1/ for capture, dob-verify,
  yesno-verify, liveness-token and refresh-nonce. The frontend now prefers
  these over admin-ajax.php because WAFs (LiteSpeed, Cloudflare, Hostinger)
  routinely block large x-www-form-urlencoded base64 image uploads to the
  generic admin-ajax.php endpoint, which manifested as empty-body 403s.
* New: `data-rest-url` attribute on the gate wrapper so veriself-mode1.js
  can route DOB and Yes-No verification through the REST API too.
* Fix: extracted `ajax_verify_inner()` and `ajax_yesno_verify_inner()` so
  the AJAX and REST handlers share the same verification pipeline. The
  AJAX handlers now also do an explicit `wp_verify_nonce` instead of the
  `check_ajax_referer` shorthand so they return the same structured E401
  JSON the REST routes use.
* Tests: 20 ad-hoc checks, full suite at 836/3334 still green.

= 4.2.9 - 2026-07-19 =
* Removed: legacy WP_DEBUG-gated debug dump of logbook SHOW INDEX rows.
  The dump was used to diagnose the v4.2.2 request_id CHAR(32) vs CHAR(36)
  schema migration; it no longer belongs in the schema-upgrade path.

= 4.2.8 - 2026-07-19 =
* Fixed: Stale-nonce "Security check failed" on pages served from a full-page
  cache. Added an unauthenticated `veriself_refresh_nonce` AJAX endpoint that
  mints a fresh capture nonce for the current visitor session, rate-limited
  to 20 requests per 5 minutes per client IP.
* Fixed: Frontend now fetches a fresh nonce on widget init and automatically
  retries capture once on E401 with the new nonce.

= 4.2.7 - 2026-07-19 =
* Fixed: Duplicate form field IDs across multiple gate renders. Each gate instance
  now gets a unique suffix on DOB inputs, consent checkboxes, and action buttons.
* Fixed: Consent checkbox lookup in JavaScript is now scoped to the active gate
  container instead of relying on global IDs.
* Fixed: Moved textdomain loading to plugins_loaded (priority 0) so WordPress 6.7+
  no longer warns about "_load_textdomain_just_in_time was called incorrectly".

= 4.2.6 - 2026-07-19 =
* Fixed: AI capture and liveness-token endpoints now return structured JSON
  instead of WordPress "Security check failed" HTML when nonce verification fails.
* Improved frontend diagnostics for HTTP 403 / blocked AJAX requests.
* Server logs now record the first 10 characters of a rejected nonce to aid
  debugging stale/cache-related nonce failures.

= 4.2.5 - 2026-07-19 =
* Fixed: admin default verification method now correctly controls which section is active in the popup.
* Fixed: settings validation prevents disabling all verification methods.
* Fixed: DOB verification buttons now share primary/secondary styling with Yes/No and AI Capture.
* Added: DOB section now provides a secondary "No / close" button matching Yes/No behavior.
* Fixed: admin checkbox fields render a hidden value="0" fallback so unchecked boxes are explicitly saved as disabled.
* Fixed: DOB and Yes/No AJAX handlers return a structured E500 error for genuine server failures.

= 4.2.4 =
* Fix: AI face default mode now propagated to popup wrapper; fallback to first enabled method if default is disabled.
* Fix: Settings validation prevents disabling all verification methods.
* Fix: DOB consent checkbox moved above verify button for consistency with Yes/No.
* Fix: DOB/Yes-No success now reloads the page so the verified cookie is respected and the popup stays closed.
* Fix: Invalid DOB dates are rejected client-side before reaching the server.
* Fix: Risk Signals admin fields are hidden when Risk Engine is disabled.

= 4.2.3 - 2026-07-18 =
* Fix: when AI face is the default verification mode in a popup, the AI section and camera widget are now activated immediately on initial load. Previously all method sections were visible at once and the camera UI was hidden until switching tabs.
* Fix: server-side HTML now marks only the default-mode section as active; other enabled sections are hidden by default.
* Tests: added regression test for default AI face mode rendering with active/hidden sections and active tab.

= 4.2.2 - 2026-07-18 =
* Fix: widened wp_veriself_logbook.request_id from CHAR(32) to CHAR(36) so UUIDs with hyphens no longer fail DB validation. Existing installs get an ALTER TABLE migration.
* Fix: added request_id length and value to error logging when logbook insert fails.
* Tests: added regression test for 36-character UUID request_id insertion.

= 4.2.1 - 2026-07-18 =
* Fix: filled empty msgstr entries in en_US and en_GB .po files so visible labels like Maintenance render correctly under UK English locale.
* Docs: added English locale .po empty-msgstr guideline to code-guides.md.
* Tests: added regression test asserting English locale .po files have no empty translations for visible tab labels.

= 4.2.0 - 2026-07-18 =
* Fix: added Admin::label() helper that falls back to the English source string when a translation catalog contains an empty entry; applied to Maintenance tab to prevent empty sidebar labels.
* Docs: added visible-label fallback guideline to code-guides.md explaining why the Maintenance label disappeared while other tabs rendered.
* Tests: added regression test for Admin::label() fallback behavior.

= 4.1.9 - 2026-07-18 =
* Fix: removed trailing spaces from HTML class attributes in admin and frontend templates; conditional class fragments now include the leading space inside the PHP block.
* Docs: added HTML class-attribute trailing-space rule to code-guides.md.
* Tests: added regression test asserting no class attribute ends with a trailing space in admin sidebar markup.

= 4.1.8 - 2026-07-18 =
* Fix: admin sidebar tab labels now have a hardcoded fallback so the Maintenance label is never empty if a translation returns an empty string.
* Fix: removed unused count_all_logbook_rows helper from AjaxTest.

= 4.1.7 - 2026-07-18 =
* Fix: client logbook insert now self-heals when the table schema is out of date (e.g. renamed legacy table missing new columns) and retries with a minimal column subset.
* Fix: log insert failures are now always written to PHP error_log, even without WP_DEBUG, to diagnose empty client logbooks.
* Fix: capture endpoint now catches \Throwable (not just \Exception) and still attempts to log an error row.
* Fix: E005 empty-token suppression only applies when no request_id is present; real attempts with a request_id now get a fallback log entry.
* Fix: removed undefined-variable warning in Logbook::create_table() SHOW INDEX query.

= 4.1.6 =
* Fix: admin Verification Log search no longer breaks without a nonce; all list columns now guard against missing/null fields (PHP 8 safe).
* Add regression tests for column_user, created_at, location, message, and search nonce handling.

= 4.1.5 =
* Fix: migrate orphan rows from wp_veriself_log to wp_veriself_logbook so the admin Verification Log displays existing entries when both tables exist.
* Add regression test for orphan log table migration.

= 4.1.4 =
* Fix: successful AI verifications were sometimes invisible in the client
  logbook because the success log stored the full raw proxy response in the
  `message` column (VARCHAR(500)). On MySQL strict mode this silently failed
  the entire INSERT, so no log row was created even though the proxy and
  frontend reported success.
* `class-ajax.php`: success logs now write a short human-readable message
  ("AI verification approved: {age} years, {gender}.") and keep the full
  raw response in the compressed `_detail` table where it belongs.
* `class-logbook.php`: `add_entry()` now defensively truncates any message
  longer than 500 bytes before inserting, so future long messages can never
  kill the log row again.
* Added regression tests: long-message truncation, success-log short message,
  and the existing real-logger success path.
* Full client suite: 801 tests, 3214 assertions, 0 failures.

= 4.1.3 =
* UX: Show verified badge now produces a visible "Verified" indicator inside
  the age-gate popup success card (controlled by the existing show_verified_badge
  admin toggle). Previously the toggle only attached a small badge to content
  gate wraps, so the popup showed no visible effect.
* UX: The .vrs-verified-badge CSS class is no longer display:none; it now
  renders as a small pill ("✓ Verified") above the success text in the popup.
* UX: Field description for "Show footer privacy link" now mentions that
  the privacy policy page must be set under Settings → Privacy in WordPress,
  otherwise the footer link is silently hidden.
* Cleanup: removed the duplicated "Shortcode Reference" section from the
  Content Gate tab. The canonical shortcode documentation now lives only in
  the Support tab (render_support_page), which already documents every
  shortcode and block.
* Regression tests: 3 new tests in AdminReorganizationTest covering the
  shortcode section move, the verified-badge CSS visibility, the badge
  injection in mode1.js, and the show_footer_links field description.
* Full client suite: 799 tests, 3203 assertions, 0 failures.

= 4.1.2 =
* Hotfix: disabling the DOB check in admin no longer leaves the DOB tab visible
  in the frontend popup. `Mode1::get_enabled_modes()` was treating `show_dob` as
  an OR-condition for the DOB verification method, so an admin who unchecked
  "Enable Date of Birth" but left "Show DOB sub-field controls" on would still
  see the DOB tab in the popup.
* `show_dob` is now strictly a UI sub-field display toggle and no longer affects
  the list of enabled verification methods. Only `enable_dob` enables the DOB
  method. The admin field description for `show_dob` was updated to reflect its
  narrowed scope.
* Regression tests: `Mode1GateTest` adds six tests covering `get_enabled_modes()`
  and the rendered popup HTML for each combination of the three mode flags.
* Coverage: class-mode1.php methods 51.35% -> 56.76%, statements 74.04% -> 75.96%.
  Full client suite: 796 tests, 3192 assertions, 0 failures.

= 4.1.1 =
* Hotfix: prevent infinite recursion in VeriselfAPI.resetVerification() retry loop.
  When admin-ajax.php returned 403 the retry path could re-enter resetVerification
  via the stored _veriselfRetryCallback and stack-overflow.
* resetVerification() now uses a re-entrancy guard (vrsResetting) and clears the
  stored callback before invoking it.
* veriself-frontend.js retryAction still calls resetVerification() to restore the
  Mode 1 gate state; the guard makes the call safe and idempotent.
* No PHP/DB changes; ships the same 4.1.0 class set with patched JS only.

= 4.1.0 - 2026-07-15 =
* Feature: Content Gate block and shortcode now support a `display` method attribute: `inline`, `popup`, `automatic`, or `onclick`. Legacy `reveal` values map to the new display modes for backward compatibility.
* Fix: Gutenberg editor save no longer freezes when age verification is enabled; the `BlockListBlock` filter no longer mutates shared `props`.
* Fix: Method-choice tabs are completely hidden and disabled as soon as AI capture/analysis starts, preventing users from switching methods mid-verification.
* Tests: Added regression tests for content gate display modes, Gutenberg save stability, and method-tab lock behavior.
* Release: Rebuilt minified JS/CSS, regenerated translations, and rebuilt `veriself-latest.zip`.

= 4.0.4 - 2026-07-13 =
* Server-side overlay masking: browser sends full-colour frame; server blacks out non-hole pixels, grayscales, and crops before AI.
* Debug photos: saves colour original and masked AI input pair.
* Logbook Photo column now prefers the colour audit photo.

= 4.0.3 - 2026-07-13 =
* Yes/No mode now shows a consent checkbox and requires it server-side when consent is mandatory, matching DOB behavior.

= 4.0.2 - 2026-07-13 =
* Fix: Removed the "Remember me" checkbox from the DOB gate UI, defaults, admin settings, JS, and CSS to align with the v4.0.x verification flow.
* Fix: E005 "Session expired" false positives resolved by prefetching a fresh liveness token immediately before capture, with a one-time retry fallback.
* Fix: Camera permission is now requested immediately and independently of the liveness-token fetch so browsers do not suppress the prompt.
* Fix: Popup AI verification now behaves identically to the inline AI shortcode: server-side token prefetch, unified strings, and consistent fallback.
* Fix: Popup AI result details are now visible on the white popup background with explicit text color override.
* Fix: Post-result method switching is now locked after popup AI success; failures show a "Try Again" button that resets the whole widget.
* Fix: Ultimate Member integration settings are automatically disabled when UM is not active.
* Fix: Maintenance tab license status markup was repaired (removed duplicated/unclosed `<div class="vrs-license-row">`).
* Tests: Added regression tests for DOB remember-me removal, E005 retry, camera timing, popup/inline unification, result visibility, method lock/retry, UM auto-disable, and maintenance tab HTML balance.
* Release: Rebuilt minified JS/CSS, regenerated translations, and rebuilt `veriself-latest.zip`.

= 4.0.0 - 2026-07-09 =

* Breaking: Content-gate shortcode renamed from `[veriself_gate]` to `[veriself_content_gate]`. The old shortcode is no longer registered (no backwards compatibility required pre-launch).
* Breaking: Gutenberg blocks split into `veriself/verify` (Age Gate) and `veriself/content-gate` (Content Gate). The Age Gate block only exposes `mode` and `minAge` overrides; the Content Gate block exposes `mode`, `minAge`, and `revealMode`.
* Change: Mode1 gate HTML now renders only the active mode section (DOB, Yes/No, or AI face). DOB and Yes/No modes no longer output the AI camera container, eliminating the double-overlay bug.
* Change: Content gate respects per-instance mode overrides (`mode` / `default_mode`) and falls back to the global `default_mode` setting.
* Fix: Frontend JS only initializes the camera widget when the active/default mode is `ai_frontal`, so DOB/Yes/No gates never request camera access.
* Tests: Added `Mode1GateTest.php` and extended `ContentGateTest` for the renamed shortcode and mode overrides.

* Breaking: The WordPress plugin install folder and main file are now `veriselfverification` instead of `age-gender-verify`. This is a manual-upgrade release: deactivate the old plugin, replace the folder with `veriselfverification/`, then activate the new plugin. All settings and verification logs are preserved because option keys are unchanged.
* Change: Translation textdomain remains `veriself` for backward compatibility; the new `veriselfverification.php` entry file explicitly loads the `/languages/` directory so translations keep working even though the install folder changed.
* Change: WPForms integration settings are now shown in their own **WPForms** admin section.
* Change: Ultimate Member integration settings are now shown in their own **Ultimate Member** admin section.
* Change: WooCommerce integration settings remain in their own **WooCommerce** section; the old single "Integrations" card is removed.
* Fix: Frontend E005 "Session expired" errors now retry once automatically by requesting a new liveness token via AJAX and resubmitting the same capture, so users are not interrupted when a transient liveness session expires between capture attempts.
* Fix: Verification attempts that reached the success path without writing a logbook row now get a fallback entry via `ensure_log_entry()` so every attempt (success or failure) is always logged.
* Fix: WPForms builder "Display Mode" and other custom select options no longer render with leading whitespace inside the option element, which caused the text to appear invisible in the WPForms-styled dropdown.
* Fix: WPForms form preview now shows a clear verification control: "Verify Age" button for popup mode and an inline placeholder for inline mode.
* Fix: WPForms `min_age` number input option now closes the `<input>` tag before the description paragraph, fixing malformed builder HTML.
* Fix: Admin settings sidebar labels are now wrapped in `.vrs-sidebar-label` so they stay visible when a theme or plugin hides bare text nodes (reported on Maintenance tab). Regression test added.
* Fix: Provider gender was returned by the backend but not surfaced on screen — `veriself-frontend.js` now skips empty `data.gender` values and the success response regression test locks in `age`, `gender`, and `message` for the reported 48/male case.
* Tests: Added `SlugRenameTest.php`, `AdminReorganizationTest.php` split-section assertions, and `E005RetryFrontendTest.php`. Updated `BuildTest.php`, `SecurityEdgeCaseTest.php`, and `AdminTabSplitTest.php` for the new slug and split sections. Added `AjaxTest` fallback-log regression tests.
* Tests (coverage): Added `SupportTest`, `GeoLocationServiceTest`, `Mode1SessionProviderTest`, `LogbookLoggerTest`, and `BlockTest`. Expanded `PrivacyTest`, `ContentAuditTest`, and `WPFormsFieldTest`. Added `CustomizerRuntimeTest` covering Customizer registration, controls, color/theme detection, and filterable accent sources. Expanded shared stubs with `WP_Customize_Color_Control` and theme-mod support. Client suite: **742 tests, 3013 assertions, 0 failures, 8 skipped**, line coverage **71.32%**.

= 3.5.0 - 2026-07-06 =

* Feature: New **White-Label** admin tab (Agency+). Rebrand the plugin admin experience with a custom plugin name, custom sidebar logo, and custom support contact details (name, email, URL). The Support tab automatically shows the configured contact information.
* Change: Removed the unused watermark feature and `show_watermark` setting. Eliminates WP.org Guideline #10 review risk and simplifies the free/paid branding story.
* Tests: Added `WhiteLabelTest.php`; updated `AdminReorganizationTest`, `AdminTabSplitTest`, and `FreemiusTest` for removed watermark and new tab. Client suite: **590 tests, 1886 assertions, 0 failures, 18 skipped**.

= 3.4.3 - 2026-07-05 =

* Performance: All custom DB tables (`wp_veriself_logbook`, `wp_veriself_logbook_detail`, `wp_veriself_gated_content`) now use `ENGINE=InnoDB`; activation converts older MyISAM installs so the detail-table FK is enforced.
* Performance: Added full-column `idx_ip_hash (ip_hash)` plus covering `idx_admin_list`, `idx_photo_purge`, and `idx_consent_purge`; dropped low-value standalone `idx_status`/`idx_created`.
* Performance: `Logbook::enforce_limit()` now counts first and deletes only the overage in 100-row chunks, avoiding the previous full-table delete.
* Performance: `Logbook::purge_expired_data()` computes per-row due dates in PHP and issues sargable batch `UPDATE`s/`DELETE`s against `created_at` indexes.
* Performance: Added `Logbook::cleanup_session_transients()` to purge stale `veriself_session_*` and `veriself_liveness_*` transients from `wp_options`.
* Performance: DI container uses lazy singleton bindings and auto-wires constructor dependencies for `Debug`, `Credits`, `Verify`, `Consent`, `RiskEngine`, and `WooCommerceIntegration`.
* Performance: `Mode1::is_visitor_verified()` is now memoized; assets only enqueue when a gate is actually rendered; bot skip list expanded; liveness tokens generated lazily via AJAX instead of on every page view.
* Performance: `ContentGate::is_gated()` checks the `wp_veriself_gated_content` index table first, then falls back to post/category meta; `reindex()` rebuilds in paginated batches without a long transaction.
* Build: Production ZIP now ships `.min.css` and `.min.js` files when `SCRIPT_DEBUG` is off; JS minified with `terser`.
* Tests: Updated `LogbookTest` index assertion, shared `wpdb` stubs for numeric `WHERE` equality and constant `SELECT` columns, `ContentGateTest` reindex revision handling. Client suite: **542 tests, 1755 assertions, 0 failures, 18 skipped** (environment/research skips).

= 3.4.1 - 2026-07-04 =

* Feature: New quick-setup wizard appears on first activation and can be relaunched from Maintenance → Quick Setup. Walks admins through Industry → Theme → Verification & Compliance → Activation in four steps.
* Feature: 5 pre-designed gate themes (Adult/Dark, Vape/Bright, Alcohol/Elegant, Cannabis/Nature, Gambling/Luxury) with theme-specific color palettes, typography, spacing, yes/no button colors, content-gate colors, and theme-specific outline SVGs.
* Feature: Industry presets set age limit, default gate copy, default theme, and verification strictness. Supports country/legislation-aware minimum age when manual country is selected.
* Change: Auto-enables VeriSelf Cloud AI trial for fresh installs so the plugin is ready to use immediately.
* Change: Simplified Labels & Text settings — removed the dedicated `veriself_labels` option group and tab. Camera/error/verified/gender labels are now standard WordPress translation strings (`__()`), while gate copy remains editable under Gate Design.
* Fix: Stopped auto-importing bundled outline SVGs into the Media Library on every admin page load, preventing duplicate attachment uploads. Old auto-imported attachments are cleaned up on upgrade/uninstall.
* Fix: Removed dead `\Exception` catch block in Freemius initialization (kept `\Throwable`).
* Fix: `Customizer::sanitize_bool()` now returns a real boolean instead of an integer.
* Dev: Added `.github/workflows/ci.yml` running PHPUnit for client, proxy, and licensing on PHP 8.2–8.5 on every push.
* Tests: Added `WizardTest.php`; updated admin tests for removed Labels & Text tab. Client suite: **526 tests, 1735 assertions, 0 failures, 14 skipped**.

= 3.4.0 - 2026-06-25 =

* Fix: Freemius plan detection now uses the live plan IDs (50466 Free, 51686 PRO, 51688 Agency, 51689 Enterprise) so paid licenses correctly unlock premium features.
* Fix: "PRO Single" and other Freemius plan aliases now map to the unified PRO plan; removed obsolete "PRO Commercial" references.
* Change: Free users with a paid VeriSelf Cloud AI add-on get all PRO plugin features except BYOAI provider selection (1 site only).
* Change: BYOAI / "Own AI" provider selection is now restricted to users with a paid plugin license (PRO/Agency/Enterprise).
* Improvement: Admin license sync refreshes the live Freemius SDK state and clears plan/add-on caches on every VeriSelf admin page load to avoid stale license state.
* Improvement: Cloud AI provider UI distinguishes free users, Cloud AI add-on users, and paid plugin-license users more clearly.
* Tests: Added/updated Freemius plan detection and BYOAI gating tests. Client suite: **497 tests, 1640 assertions, 0 failures, 14 skipped**.

= 3.3.9 - 2026-06-24 =

* Improvement: Frontend face-region crop — the grayscale webcam frame is now cropped to the transparent face hole(s) in the configured SVG overlay before being sent to the AI provider. The overlay stays visible to the user for alignment, but the darkened background and feature guides are no longer baked into the analysed image. This reduces token/pixel usage and keeps eyes, nose, mouth, and chin clearly visible.
* Improvement: Liveness thumbnails are cropped to the same face region for consistency.
* Improvement: Full-frame colour debug photo remains available for admin audit when debug mode is enabled.
* Tests: Added `FaceCropTest.php` validating crop-box detection for single/couple overlays and custom SVGs. Client suite: **TBD tests, TBD assertions, 0 failures, 9 skipped**.

= 3.3.2 - 2026-06-21 =

* Refactor: Migrated Debug, RiskEngine, Verify, Consent, Privacy, PreRegister, and WooCommerceIntegration from static `Plugin::get_option()` to constructor-injected `Settings` dependency.
* Refactor: Introduced singleton facade (`instance()` / `set_instance()`) across all refactored classes for DI container compatibility.
* Refactor: Registered all refactored classes in the DI container with lazy fallback in `instance()`.
* Tests: Updated tests to use `set_instance()` or pass mock `Settings`; added WooCommerceGatingTest singleton initialization. Client suite: **438 tests, 1494 assertions, 0 failures, 9 skipped**.
* Chore: Bumped version to 3.3.2.

= 3.3.1 - 2026-06-20 =

* Refactor: Converted `Credits` from a static utility to an instance-based service with constructor-injected `Settings`, `LicenseProvider`, and `GeoLocationProvider` dependencies.
* Refactor: Introduced `LicenseProvider` contract and `FreemiusLicenseProvider` adapter to decouple credit logic from the static `Freemius` class.
* Refactor: Updated `Ajax` to receive `Credits` via DI instead of static calls.
* Refactor: Registered `Credits` and `LicenseProvider` in the DI container.
* Tests: Rewrote `CreditsTest.php` for instance-based service; added `FreemiusLicenseProviderTest.php`. Client suite: **425 tests, 1462 assertions, 0 failures, 9 skipped**.

= 3.2.0 - 2026-06-18 =

* Feature: Gated content audit CSV export now supports date-range filtering and includes a UTF-8 BOM for Excel compatibility.
* Fix: CSV export now terminates with `wp_die()` after sending headers to prevent accidental output continuation.
* Fix: Removed dead `default_gate_mode` setting validation; `default_mode` is the canonical verification-mode setting.
* Fix: Confirmed removal of the legacy "Let me decide" AI toggle; only mode tabs remain.
* Accessibility: WCAG contrast fix — replaced low-contrast `#9ca3af` with `#6b7280` across frontend gate text, admin Freemius feature list, and block editor preview.
* Code Quality: Added `declare(strict_types=1)` to all client production files and tests (Freemius SDK excluded); fixed strict-type errors in `class-mode1.php`.
* Tests: Added `ContentAuditTest.php` covering CSV BOM, date-range query, and nonce checks. Client suite: **420 tests, 1449 assertions, 0 failures, 9 skipped**.

= 3.0.4 - 2026-06-11 =

* Fix: Admin UI tab content bleeding — fixed missing closing angle brackets in card HTML
* Fix: API provider disclaimer text updated from "Free proxy mode" to accurate "VeriSelf Cloud AI" messaging; styling changed from intrusive red to muted grey
* Fix: "VeriSelf cloud proxy" naming corrected to "VeriSelf Cloud AI" across all UI strings and JS
* Fix: E018 false positive when a provider is healthy — retry loop now correctly returns error when all providers exhausted after any number of attempts

= 3.0.3 - 2026-06-11 =

* Security: Frontend proxy error leak fixed — class-openai.php returns generic user-safe messages only; class-ajax.php adds user_facing_message() mapping. Never exposes HTTP status, provider names, or raw JSON to end users.
* Fix: proxy_error, proxy_rejected, proxy_parse_error added to error_codes mapping table (were falling back to E999)
* Update: Frontend gate design system migrated — veriself-gate-* → vrs-gate-* across PHP, CSS, JS, and E2E tests
* Update: Dashboard widget design system migrated — veriself-dash-* → vrs-stats-*
* Update: Logbook inline color styles replaced with vrs-score-*, vrs-status-*, vrs-muted CSS classes
* Update: Version bumped to 3.0.3

= 3.0.2 - 2026-06-11 =

* Fix: Provider rotator migrated from `wp_options` to `wp_veriself_provider_health` custom table with atomic SQL updates
* Security: Provider auth keys encrypted with AES-256-GCM via `AUTH_KEY`
* Update: Proxy admin shows encrypted key fields (password input) with "re-enter to change" placeholder
* Update: OpenSSL missing warning (red admin notice) when extension unavailable
* Update: AUTH_KEY rotation detection — red notice when encrypted provider keys cannot be decrypted
* Update: Proxy version bumped to 3.0.2

= 3.0.1 - 2026-06-11 =

* Fix: Updated website pricing across all pages (PRO €99/yr or €9/mo, Agency €199/yr, Enterprise €499/yr; Cloud AI Lite €5/mo, Pro €15/mo, Business €45/mo)
* Fix: Freemius webhook plan mapping — Business tier now correctly maps to 750 verifications/month (was 500)
* Update: Mandatory semantic versioning added to coding guidelines and release process
* Update: Version bumped to 3.0.1 across client, proxy, and licensing plugins

= 3.0.0 - 2026-06-10 =

* BREAKING: New business model — "Bring Your Own AI"
* Feature: Simplified tiers — PRO (€99/yr), Agency (€199/yr), Enterprise (€499/yr)
* Feature: All paid tiers include ALL plugin features (couple mode, GPS, fraud scoring, integrations, theme customization)
* Feature: VeriSelf Cloud AI monthly subscriptions — Lite €5/mo (50 verifications), Pro €15/mo (200 verifications), Business €45/mo (750 verifications)
* Feature: Free tier changed to 100 lifetime AI verifications via proxy + Age Gate popup forever
* Feature: "Bring your own provider" setup flow — users can connect self-hosted Ollama, Ollama cloud, ChatGPT, Claude, OpenCode, or any OpenAI-compatible endpoint
* Fix: Credits system simplified — proxy is single source of truth, client no longer double-decrements
* Fix: Admin UI updated to reflect new pricing and proxy subscription status
* Update: Freemius plan metadata updated to v3.0 tiers (free/pro/agency/enterprise)
* Update: All 221 tests pass (0 failures, 0 errors)

= 2.2.1 - 2026-06-09 =

* Fix: Default headline/subtext preserved when settings form submitted with empty values
* Fix: Progressive disclosure flash eliminated — conditional rows default to hidden via CSS, revealed by JS after state is known
* Feature: Gutenberg block enhanced — 7 new attributes (mode, startMode, allowModeSwitch, minAge, redirect, redirectType, align), Inspector Controls, alignment toolbar, visual preview
* Feature: Shortcode wired to block attributes — all block settings pass through to frontend container
* Fix: PRO license sync after upgrade — detects plan from Freemius account/license object on admin_init

= 2.1.0 - 2026-06-06 =

* Feature: AI confidence threshold — new Expert setting (0.1–1.0, default 0.7). AI prompt requests confidence score; verification rejected if below threshold. New error code E023.
* Feature: Fraud scoring per capture — GeoFraud::score_fraud() runs on every verification (IP vs GPS mismatch, VPN detection). New fraud_score INT column in log table.
* Feature: Fraud max score setting — configurable threshold (0–100, default 80) under Compliance tab with block/review/log actions. New error code E024, new 'review' log status.
* Improvement: Age Gate defaults — headline now 'Are you old enough?', subtext now 'This site contains age-restricted content.'
* Improvement: Logbook UI — removed Model column, Details column now opens a modal popup with full entry data (status, age, gender, location, risk score, photo, raw response). Message truncated with tooltip on hover.
* Improvement: Details column changed from truncated text span to 'View Details' button opening modal popup with all log entry data.

= 2.0.4 - 2026-06-03 =

* Feature: Age Gate v2 redesign — card-based popup with logo/icon (PRO), DOB fields (month/day/year auto-advance), AI verification toggle inside same popup, inline error messages replacing alert(), verifier button with consent + remember-me, Customizer color vars, country-specific DOB format.
* Feature: Admin redesign — sidebar layout (Setup/Gate/Content/System + Labels + Support tabs), flat stacked cards (no accordion), conditional field visibility JS, auto-save on change.
* Feature: Re-verification lockout — human-readable remaining wait time displayed in E002 error.
* Feature: Inline error messages (Mode 1) — all alert() calls replaced with veriselfShowError() targeting .veriself-gate-error div.
* Improvement: Vision model detection — added llama3.2-vision, phi-3-vision, pixtral, idefics, nvlm, xcomposer, emu3. Removed false-positive 'gemma'.
* Improvement: Defensive AJAX error handling — handle_capture() wrapped in try-catch + handle_capture_inner(). Output buffer cleaning before JSON response.
* Improvement: Country DOB format — expanded to 60+ countries, default changed from mdy to dmy.

= 2.0.0 - 2026-05-29 =

* BREAKING: Rebranded to VeriSelf — plugin file renamed to veriself.php, age-gender-verify.php kept as migration stub
* BREAKING: Namespace changed from AGV to VeriSelf
* BREAKING: All option names changed from agv_* to veriself_* (auto-migrated on upgrade)
* BREAKING: All user meta keys changed from agv_* to veriself_*
* BREAKING: DB table renamed from wp_agv_log to wp_veriself_log
* BREAKING: All AJAX actions, nonces, transients, cron hooks renamed from agv_* to veriself_*
* BREAKING: Shortcode changed to [veriself_verify] ([agv_verify] kept as alias)
* BREAKING: CSS/JS handles, classes, file names changed from agv-* to veriself-*
* Improvement: Updated default proxy URL to http://80.60.70.170:11435
* Improvement: Auto-migration from v1.x options on first admin load
* Internal: Full PHP namespace rebranding and code cleanup

= 1.7.8 =
* Face feature guide marks (eyes, nose, chin) added to SVG outline overlays
* Liveness: thumbnails downsampled to 32x32 (was 16x16) for better blink sensitivity
* Blink threshold adjusted from +20 to +8
* Debug data included in successful responses when Debug Mode is enabled

= 1.7.7 - 2026-05-26 =

* Fix: import_default_outlines() now compares URL paths instead of full URLs — fixes silent skip on HTTP/HTTPS scheme mismatch during upgrades
* Enhancement: import_default_outlines() now uses attachment_url_to_postid() to verify existing attachments (more reliable than strpos check)
* Enhancement: import_default_outlines() now runs on admin_init hook — if import fails transiently, it self-heals on next admin page load
* Documentation: Updated AGENTS.md with version history

= 1.7.6 - 2026-05-26 =

* Enhancement: Liveness failure now includes debug data in WP_Error error_data — max_pair_diff, pair_ratios[], variance_curve[], best_blink values
* Enhancement: Debug mode logs liveness failure data to PHP error log with [AGV Liveness Debug] prefix and includes it in JSON error response as debug field
* Documentation: Updated AGENTS.md with threshold values and debug approach

= 1.7.5 - 2026-05-26 =

* Fix: Liveness check no longer silently passes when fewer than 3 thumbs sent (was a security gap)
* Enhancement: Capture frame count increased from 6 to 10 — better blink detection window
* Fix: Added defensive exit after liveness failure wp_send_json_error to ensure script termination

= 1.7.4 - 2026-05-26 =

* Fix: Logbook table now shows Model column (previously stored but never displayed)
* Fix: raw_response now stored in both message and raw_response DB columns
* Enhancement: Debug logging in analyze_openai() and analyze_ollama() — logs URL, model, HTTP status, and response body when Debug Mode enabled

= 1.7.3 - 2026-05-26 =

* Fix: raw_response and model now passed through from API response to log entries (was always empty in debug log)
* Fix: Overlay import runs on every version upgrade (not just < 1.5.3) and handles unset URL keys
* Fix: Instance headers (X-AGV-Version, X-AGV-Site, X-AGV-IP) now always sent even without instance key
* Enhancement: Proxy log now shows website, plugin version, and server IP for all callers
* Enhancement: Proxy CORS allows X-AGV-IP header
* Enhancement: Rate limiting keyed on plugin-reported server IP (X-AGV-IP)

= 1.7.2 - 2026-05-26 =

* Change: Prompt moved to Ollama::PROMPT constant — no longer stored in DB or editable in admin
* Security: Prompt injection protection via Ollama::SYSTEM_MSG — OpenAI uses system role, Ollama uses [SYSTEM]...[/SYSTEM] prefix
* Enhancement: is_vision_model() filters model list to vision-capable models only (22 keyword matches)
* Fix: Expert Settings collapse toggle now works correctly (CSS class injection by field ID, ▸/▾ swap)
* Fix: SVG overlay canvas taint fixed — preloadOutline() fetches SVG as text then creates Blob URL
* Change: Blink detection threshold relaxed from 0.0005 to 0.0025
* Change: Liveness token TTL increased from 120s to 300s

= 1.7.1 - 2026-05-26 =

* Fix: Trial API type — JS select id attribute fixed
* Fix: Trial URL/model purely server-side (never in DB or HTML)
* Change: Prompt/temp/timeout moved to Maintenance tab under collapsed "Expert Settings"
* Fix: Infinite recursion in get_effective_settings() resolved
* Chore: namespace AGV + \WP_Error prefix added to class-ollama.php
* Migration: Existing prompt/temp/timeout values auto-migrated from agv_provider to agv_maintenance

= 1.7.0 - 2026-05-26 =

* Feature: Trial API type — 10 calls/day via proxy, URL/model hardcoded server-side
* Feature: Ollama rate-limit proxy (Node.js, zero npm deps)
* Enhancement: Prompt/temp/timeout moved to Maintenance > Expert Settings (collapsed by default)
* Feature: Instance tracking headers (X-AGV-Key, X-AGV-Version, X-AGV-Site)
* Enhancement: Model field changed to editable <datalist> dropdown
* Fix: Remote proxy deployment (NSSM service, BIND_HOST=0.0.0.0, OLLAMA_HOST=ageverify.groupfap.com)

= 1.6.3 - 2026-05-24 =

* Enhancement: Plugin instance tracking headers sent on all API calls

= 1.6.2 - 2026-05-24 =

* Enhancement: Model field changed to editable <datalist> dropdown for flexible model input

= 1.6.1 - 2026-05-24 =

* Fix: Bearer token prefix now added automatically for Authorization header — OpenAI users can enter the raw API key (sk-...) instead of manually prefixing "Bearer "
* Enhancement: Added `get_auth_header()` helper method for consistent auth handling across all API calls

= 1.6.0 - 2026-05-24 =

* Enhancement: Model field now includes a quick-select dropdown populated from the API — shows all available models (via /v1/models or /api/tags), works with both Ollama and OpenAI-compatible APIs, text input retained for custom typing
* Enhancement: Added `Ollama::list_models()` and AJAX endpoint `agv_list_models` for model discovery

= 1.5.9 - 2026-05-24 =

* Fix: Outline images now visible in media library — added `upload_mimes` filter to register SVG support, expanded media picker library filter to include SVG MIME types
* Enhancement: Re-verification setting now supports custom interval in days (1–365) via a new "Custom interval" option, while preserving backward compatibility with old month-based presets

= 1.5.8 - 2026-05-24 =

* Fix: Toggle switches now render correctly — native checkbox is fully hidden with a visual slider element, eliminating the double checkbox appearance in WordPress admin

= 1.5.7 - 2026-05-24 =

* Fix: Syntax error in `class-admin.php` — missing closing quote in dashicons HTML attribute caused fatal error on settings page load

= 1.5.6 - 2026-05-24 =

* Enhancement: Complete admin UI redesign — modern card-based layout, toggle switches for checkboxes, clean tab navigation with icons, improved form spacing and typography
* Enhancement: New admin CSS (`assets/css/agv-admin.css`) with responsive design and modern WordPress dashboard styling

= 1.5.5 - 2026-05-24 =

* Security: Improved Ollama prompt — `is_live` now asks whether the image is a "manipulation to fool our age, gender validation" instead of a generic liveness description

= 1.5.4 - 2026-05-24 =

* Fix: Log photos now open via AJAX serving endpoint instead of broken data URI links
* Fix: Frontend labels now translate according to user locale when no admin override is set
* Enhancement: Added DebugTest with 6 test methods for photo path/URL utilities

= 1.5.3 - 2026-05-24 =

* Security: Liveness detection tightened — movement threshold lowered to 0.05%, blink detection now required
* Security: Ollama prompt now includes `is_live` field to detect photos/screens vs real faces
* Enhancement: Default outline SVGs auto-imported into media library on activation/upgrade

= 1.5.2 - 2026-05-24 =

* Change: Single mode now rejects images with 2+ faces detected (too_many_faces error)

= 1.5.1 - 2026-05-24 =
* Commercial: Added full commercial strategy to AGENTS.md (freemium tiers, pricing, marketing, revenue projections)
* Fix: readme.txt stable tag now matches plugin version
* Fix: Removed corner-brightness mask integrity check — SVG-to-canvas rendering is browser-fragile (CORS/taint). Replaced with basic dimension sanity. Other layers (liveness token, movement, Ollama) provide sufficient security.
* Fix: applyFaceMask() wrapped in try-catch — CORS/SVG errors no longer silently break the happy flow
* Fix: Liveness token no longer consumed on verification failure — user can retry without page reload
* Fix: Face mask is now actually called in captureFrame (was defined but never invoked)
* Chore: Added .gitignore, initialized git repo
* Chore: Disabled debug mode by default for production readiness
* Test: 35+ PHPUnit tests across 4 test files (parse_response, mask integrity, verify logic, plugin defaults)
* Test: composer.json + phpunit.xml.dist for automated test execution

= 1.0.0 - 2026-05-23 =
* Initial public release
* Feature: Webcam capture with guided head outline (single + couple mode)
* Feature: AI age and gender estimation via Ollama vision models
* Feature: GPS location with Nominatim reverse geocoding
* Feature: Configurable rate limiting with rolling window
* Feature: Re-verification options (never, always, 3/6/12 months)
* Feature: Admin verification log with photo thumbnails and bulk actions
* Feature: Debug mode with auto-cleanup and .htaccess protection
* Feature: AJAX test connection button for Ollama server
* Feature: Fully configurable color theming (12 color settings)
* Feature: Gutenberg block + [agv_verify] shortcode
* Feature: Translation ready with English + Dutch
* Security: Nonce verification on all AJAX endpoints
* Security: Capability checks on admin pages
* Security: File access protection via .htaccess
